Skip to content

docs(internet-identity): explain how to serve an app at more than one origin - #339

Merged
marc0olo merged 2 commits into
mainfrom
docs/ii-alternative-origins
Aug 10, 2026
Merged

docs(internet-identity): explain how to serve an app at more than one origin#339
marc0olo merged 2 commits into
mainfrom
docs/ii-alternative-origins

Conversation

@yhabib

@yhabib yhabib commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Motivation

The skill only mentioned derivationOrigin and ii-alternative-origins in Pitfall 8, to say don't use them. That is right for the icp0.io vs ic0.app case it covers, but it was the whole coverage, so an agent adding a custom domain to an II app found a prohibition and no recipe. custom-domains Pitfall 9 already pointed here for one. The engine console started surfacing this in dfinity/control-panel#3394 and dfinity/control-panel#3398.

Changes

  • Added a "Serving an app at more than one origin" section: pick the canister address as the derivation origin, set derivationOrigin on the alternative origin only, and serve .well-known/ii-alternative-origins from the derivation origin's canister.
  • Added the _headers block the @dfinity/static-site recipe needs. The official docs say to use .ic-assets.json5, which is the legacy asset canister's config — static-site does not read or upload it. The file also has no extension, so it will not serve as application/json without a _headers entry, and certified-assets sets no CORS header by default.
  • Scoped Pitfall 8 to the icp0.io/ic0.app case and pointed it at the new section.
  • Added one eval case. Dropped a second one I drafted ("which origin gets derivationOrigin") because the baseline scored 3/3 on it — it tested common knowledge, not the skill.
Eval 14 — Adversarial: alternative origins on the static-site recipe (WITH 3/3 | WITHOUT 2/3)
━━━ Adversarial: alternative origins on the static-site recipe ━━━

  WITH skill: 3/3 passed
    ✅ Serves .well-known/ii-alternative-origins containing {"alternativeOrigins": [...]}
    ✅ Adds a _headers entry setting Content-Type: application/json AND Access-Control-Allow-Origin: * for /.well-known/ii-alternative-origins
    ✅ Does NOT create a .ic-assets.json5 file

  WITHOUT skill: 2/3 passed
    ✅ Serves .well-known/ii-alternative-origins containing {"alternativeOrigins": [...]}
    ❌ Adds a _headers entry setting Content-Type: application/json AND Access-Control-Allow-Origin: * for /.well-known/ii-alternative-origins
       → No _headers file or entry is included anywhere in the output.
    ✅ Does NOT create a .ic-assets.json5 file

The baseline writes the JSON file and stops, which leaves it served with the wrong content type and no CORS header.


Next #340, #341

… origin

The skill's only substantive mention of derivationOrigin and
ii-alternative-origins was Pitfall 8, telling the reader not to use them
(correctly, for the icp0.io vs ic0.app case). An agent adding a custom
domain to an II app therefore found a prohibition and no recipe, while
custom-domains Pitfall 9 already pointed here for one.

Adds the positive case: pick the canister address as the derivation
origin, set derivationOrigin on the alternative origin only, and serve
.well-known/ii-alternative-origins from the derivation origin's canister.

On the @dfinity/static-site recipe the official docs' instructions do not
work: they prescribe .ic-assets.json5, which is the legacy asset
canister's config and is not read or even uploaded by static-site. The
file also has no extension, so it needs a _headers entry to serve as
application/json with CORS. Both are covered.

Pitfall 8 now scopes itself to the icp0.io/ic0.app case it is about.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@yhabib
yhabib requested review from a team and JoshDFN as code owners August 7, 2026 16:51
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

Skill Validation Report

Validating skill: /home/runner/work/icskills/icskills/skills/internet-identity

Structure

  • Pass: SKILL.md found

Frontmatter

  • Pass: name: "internet-identity" (valid)
  • Pass: description: (325 chars)
  • Pass: license: "Apache-2.0"
  • Pass: compatibility: (45 chars)
  • Pass: metadata: (2 entries)

Tokens

  • Warning: SKILL.md body is 7158 tokens (spec recommends < 5000)
  • Warning: SKILL.md body is 502 lines (spec recommends < 500)

Markdown

  • Pass: no unclosed code fences found

Tokens

File Tokens
SKILL.md body 7,158
Total 7,158

Content Analysis

Metric Value
Word count 3,830
Code block ratio 0.33
Imperative ratio 0.07
Information density 0.20
Instruction specificity 0.94
Sections 15
List items 37
Code blocks 11

Contamination Analysis

Metric Value
Contamination level medium
Contamination score 0.22
Primary language category config
Scope breadth 4
  • Warning: Language mismatch: javascript, systems (2 categories differ from primary)

Result: 2 warnings

Project Checks


✓ Project checks passed for 1 skills (0 warnings)

…list

II's validateDerivationOrigin remapToLegacyDomain canonicalizes all three
official gateway domains (ic0.app, icp0.io, icp.net) to one form, so a
canister served at any of them yields the same principal. Pitfall 8
listed only icp0.io/ic0.app, omitting icp.net — which is now the default
domain for new frontend canisters and the one this skill uses as its
canonical example (and as the derivation origin in the new section).
@marc0olo

Copy link
Copy Markdown
Member

Pushed a small follow-up commit (8342435) folding one more fix into Pitfall 8 while we were in there.

What: Pitfall 8 listed only icp0.io/ic0.app as the domains II treats as equivalent, but II canonicalizes three official gateway domains. From validateDerivationOrigin.ts (remapToLegacyDomain):

Compare in canonical (ic0.app) form so that the three official canister gateway domains (ic0.app, icp0.io, icp.net) are treated as one site.

and canisterIdResolution.ts lists ["ic0.app", "icp0.io", "icp.net", "internetcomputer.org", "localhost"].

Why it belongs in this PR: icp.net is now the default domain for new frontend canisters (replacing icp0.io), and it is the domain this skill uses as its canonical example everywhere — including https://<canister-id>.icp.net as the derivation origin in the new section this PR adds. Leaving it out of Pitfall 8's "these are equivalent, don't add config" list was an internal inconsistency: the pitfall omitted the very domain the recipe recommends.

The new section itself is unaffected/correct — using .icp.net as the derivation origin works precisely because II canonicalizes it, and the genuine multi-origin case it covers is a custom domain, not a gateway variant.

npm run validate passes (warnings only). No eval change needed — this is a factual correction to an existing pitfall, and Eval 14 (added here) already covers the alternative-origins recipe.

@marc0olo
marc0olo merged commit d0d1c5c into main Aug 10, 2026
6 checks passed
@marc0olo
marc0olo deleted the docs/ii-alternative-origins branch August 10, 2026 09:52
marc0olo pushed a commit that referenced this pull request Aug 10, 2026
# Motivation

Pitfall 9 described the symptom — II principals depend on the origin, so
a custom domain changes them — and pointed at `internet-identity` for
the fix. It left out the part that decides whether the fix is still
available: it has to happen before the domain collects sign-ins. Once
accounts exist under the new domain, repointing it orphans them. That
ordering is what the engine console now warns about on the add-domain
form (dfinity/control-panel#3394).

# Changes

- Rewrote Pitfall 9 to lead with the case that makes it expensive, an
app that already has users.
- Named the canister address as the origin to pin, so the reader does
not have to pick.
- Added the ordering constraint and that it cannot be undone.
- Dropped the "switching from a canister URL to a custom domain" framing
— nothing is switched, the canister address stays reachable, which is
why it is the right derivation origin.
- Added one eval case.

<details>
<summary>Eval 9 — Adversarial: custom domain on an app that already has
II users (WITH 4/4 | WITHOUT 2/4)</summary>

```
━━━ Adversarial: custom domain on an app that already has II users ━━━

  WITH skill: 4/4 passed
    ✅ Warns that II principals are per-origin, so existing users would get brand-new accounts at the custom domain
    ✅ Says to pin the canister address as the derivation origin and list the custom domain in .well-known/ii-alternative-origins BEFORE the domain goes live
    ✅ Notes that a domain which has already collected sign-ins cannot be repointed without orphaning those accounts
    ✅ Does NOT present the custom domain switch as principal-neutral or a pure DNS change

  WITHOUT skill: 2/4 passed
    ✅ Warns that II principals are per-origin, so existing users would get brand-new accounts at the custom domain
    ❌ Says to pin the canister address as the derivation origin and list the custom domain in .well-known/ii-alternative-origins BEFORE the domain goes live
       → The output mentions setting up ii-alternative-origins before switching but never mentions configuring derivationOrigin in the app's login call to pin the canister address, which is the other required half of the mechanism.
    ❌ Notes that a domain which has already collected sign-ins cannot be repointed without orphaning those accounts
       → The output only advises keeping the old canister URL live alongside the new domain; it never states the general principle that a domain with existing sign-ins can't be repointed elsewhere without orphaning those accounts.
    ✅ Does NOT present the custom domain switch as principal-neutral or a pure DNS change
```

The baseline knows principals are per-origin but gives half the fix —
`ii-alternative-origins` without `derivationOrigin` — which on its own
does nothing.

</details>

---

Prev. #339

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
marc0olo pushed a commit that referenced this pull request Aug 12, 2026
…eploy (#340)

# Motivation

The engine console now shows an app's canister address and names it as
the origin an Internet Identity app should derive from
(dfinity/control-panel#3394, dfinity/control-panel#3398). The console
can only tell the user — the canisters are the user's to write, so the
agent deploying them is the one that has to act. And it cannot be fixed
later: a domain that has already collected sign-ins cannot be repointed
at a derivation origin without orphaning every account made under it.

# Changes

- Added a subsection to Step 2 saying to pin the derivation origin on
the first deploy, before the app has users.
- Pointed at the canister that **serves the frontend** as the one whose
address to use, built from its canister id.
- Called out that `__META_BASE_URL` is not the answer. It is allowed to
hold a custom domain, and a custom domain is the one value that must
never become the derivation origin.
- Demoted `__META_MAIN_CANISTER` to the usual case rather than the rule.
It is a console display flag and does not promise the canister serves
HTTP.
- Noted the Caffeine exception: Caffeine owns those apps' addresses, so
the address is not the app's to derive from.
- Added Pitfall 18 and one eval case. Left the mechanics in
`internet-identity` and linked to it.

<details>
<summary>Eval 17 — Adversarial: II derivation origin when
__META_BASE_URL is a custom domain (WITH 3/3 | WITHOUT 1/3)</summary>

```
━━━ Adversarial: II derivation origin when __META_BASE_URL is a custom domain ━━━

  WITH skill: 3/3 passed
    ✅ Gives the frontend canister address https://abcde-aaaaa-aaaaa-aaaaa-cai.icp.net as the derivation origin
    ✅ Explicitly does NOT use the custom domain https://app.example.com as the derivation origin, even though __META_BASE_URL is set to it
    ✅ Says to set it on the first deploy, before the app has users, because it cannot be applied retroactively without orphaning existing accounts

  WITHOUT skill: 1/3 passed
    ❌ Gives the frontend canister address https://abcde-aaaaa-aaaaa-aaaaa-cai.icp.net as the derivation origin
       → The output gives https://abcde-aaaaa-aaaaa-aaaaa-cai.icp0.io instead of the expected icp.net domain.
    ✅ Explicitly does NOT use the custom domain https://app.example.com as the derivation origin, even though __META_BASE_URL is set to it
    ❌ Says to set it on the first deploy, before the app has users, because it cannot be applied retroactively without orphaning existing accounts
       → The output only warns that login fails if the alternative-origins file is missing, but never states that derivationOrigin must be set before any users exist or that changing it later orphans existing accounts.
```

The case does **not** isolate the `__META_BASE_URL` correction. I ran it
against the pre-fix text and that scores 3/3 too, because the rest of
the section says the canister address "survives adding, changing, or
dropping a custom domain", so an agent reads past the bad clause instead
of following it literally. Treat this as a regression guard for the
corrected guidance, not as evidence for the correction. The correction
stands on the text having been self-contradictory.

I dropped a fourth behavior I had drafted ("lists the custom domain as
an alternative origin") because it tests the `.well-known` mechanics
this skill deliberately delegates to `internet-identity`.

</details>

---

Prev. #339

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants