Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow custom domains to use alternative origins #2399

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 5 additions & 13 deletions docs/ii-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ This section describes the Internet Identity Service from the point of view of a

- the `allowPinAuthentication` (EXPERIMENTAL), if present, indicates whether or not the Identity Provider should allow the user to authenticate and/or register using a temporary key/PIN identity. Authenticating dapps may want to prevent users from using Temporary keys/PIN identities because Temporary keys/PIN identities are less secure than Passkeys (webauthn credentials) and because Temporary keys/PIN identities generally only live in a browser database (which may get cleared by the browser/OS).

- the `derivationOrigin`, if present, indicates an origin that should be used for principal derivation instead of the client origin. Values must match the following regular expression: `^https:\/\/[\w-]+(\.raw)?\.(ic0\.app|icp0\.io)$`. Internet Identity will only accept values that are also listed in the HTTP resource `https://<canister_id>.ic0.app/.well-known/ii-alternative-origins` of the corresponding canister (see [Alternative Frontend Origins](#alternative-frontend-origins)).
- the `derivationOrigin`, if present, indicates an origin that should be used for principal derivation instead of the client origin. Internet Identity will only accept values that are also listed in the HTTP resource `/.well-known/ii-alternative-origins` of the corresponding canister (see [Alternative Frontend Origins](#alternative-frontend-origins)).


6. Now the client application window expects a message back, with data `event`.
Expand Down Expand Up @@ -211,12 +211,8 @@ The Internet Identity frontend will use `event.origin` as the "Frontend URL" to

## Alternative Frontend Origins


To allow flexibility regarding the canister frontend URL, the client may choose to provide the canonical canister frontend URL (`https://<canister_id>.ic0.app` or `https://<canister_id>.raw.ic0.app`) as the `derivationOrigin` (see [Client authentication protocol](#client-authentication-protocol)). This means that Internet Identity will issue the same principals to the frontend (which uses a different origin) as it would if it were using one of the canonical URLs.

:::note
This feature is also available for `https://<canister id>.icp0.io` (resp. `https://<canister id>.raw.icp0.io`).
:::
To allow flexibility regarding the canister frontend URL, the client may choose to provide another frontend URL as the `derivationOrigin` (see [Client authentication protocol](#client-authentication-protocol)). This means that Internet Identity will issue the same principals to the frontend (which uses a different origin) as it would if it were using the `derivationOrigin` directly.
This feature works for all [custom domains](https://internetcomputer.org/docs/current/developer-docs/web-apps/custom-domains/using-custom-domains) backed by canisters.

:::caution
This feature is intended to allow more flexibility with respect to the origins of a _single_ service. Do _not_ use this feature to allow _third party_ services to use the same principals. Only add origins you fully control to `/.well-known/ii-alternative-origins` and never set origins you do not control as `derivationOrigin`!
Expand All @@ -226,11 +222,7 @@ This feature is intended to allow more flexibility with respect to the origins o
`https://<canister_id>.ic0.app` and `https://<canister_id>.raw.ic0.app` do _not_ issue the same principals by default . However, this feature can also be used to map `https://<canister_id>.raw.ic0.app` to `https://<canister_id>.ic0.app` principals or vice versa.
:::

:::note
In general, Internet Identity only allows alternative origins of the form `<canister id>.ic0.app` or `<canister id>.icp0.io`. There is one exception: `nns.ic0.app`, which is treated as `<nns-dapp canister id>.icp0.io`.
:::

In order for Internet Identity to accept the `derivationOrigin` the corresponding canister must list the frontend origin in the JSON object served on the URL `https://<canister_id>.ic0.app/.well-known/ii-alternative-origins` (i.e. the canister _must_ implement the `http_request` query call as specified [here](https://github.com/dfinity/interface-spec/blob/master/spec/index.adoc#the-http-gateway-protocol)).
In order for Internet Identity to accept the `derivationOrigin` the corresponding canister must list the frontend origin in the JSON object served on the URL `https://<canister_id>.icp0.io/.well-known/ii-alternative-origins` (i.e. the canister _must_ implement the `http_request` query call as specified [here](https://github.com/dfinity/interface-spec/blob/master/spec/index.adoc#the-http-gateway-protocol)).


### JSON Schema {#alternative-frontend-origins-schema}
Expand All @@ -240,7 +232,7 @@ In order for Internet Identity to accept the `derivationOrigin` the correspondin
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "II Alternative Origins Principal Derivation Origins",
"description": "An object containing the alternative frontend origins of the given canister, which are allowed to use a canonical canister URL (https://<canister_id>.ic0.app or https://<canister_id>.raw.ic0.app) for principal derivation.",
"description": "An object containing the alternative frontend origins of the given canister, which are allowed to use the URL this document is hosted on for principal derivation.",
"type": "object",
"properties": {
"alternativeOrigins": {
Expand Down
10 changes: 6 additions & 4 deletions src/frontend/src/flows/authorize/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { validateDerivationOrigin } from "../../utils/validateDerivationOrigin";
import { Delegation, fetchDelegation } from "./fetchDelegation";
import { AuthContext, authenticationProtocol } from "./postMessageInterface";

import { resolveCanisterId } from "$src/utils/canisterIdResolution";
import { nonNullish } from "@dfinity/utils";
import copyJson from "./index.json";

Expand Down Expand Up @@ -167,10 +168,11 @@ const authenticate = async (
const i18n = new I18n();
const copy = i18n.i18n(copyJson);

const validationResult = await validateDerivationOrigin(
authContext.requestOrigin,
authContext.authRequest.derivationOrigin
);
const validationResult = await validateDerivationOrigin({
requestOrigin: authContext.requestOrigin,
derivationOrigin: authContext.authRequest.derivationOrigin,
resolveCanisterId,
});

if (validationResult.result === "invalid") {
await displayError({
Expand Down
15 changes: 10 additions & 5 deletions src/frontend/src/flows/verifiableCredentials/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,11 @@ const verifyCredentials = async ({
// Verify that principals may be issued to RP using the specified
// derivation origin
const validRpDerivationOrigin = await withLoader(() =>
validateDerivationOrigin(rpOrigin_, rpDerivationOrigin)
validateDerivationOrigin({
requestOrigin: rpOrigin_,
derivationOrigin: rpDerivationOrigin,
resolveCanisterId,
})
);
if (validRpDerivationOrigin.result === "invalid") {
return abortedCredentials({ reason: "bad_derivation_origin_rp" });
Expand Down Expand Up @@ -307,10 +311,11 @@ const getValidatedIssuerDerivationOrigin = async ({
}
derivationOriginResult.kind satisfies "origin";

const validationResult = await validateDerivationOrigin(
issuerOrigin,
derivationOriginResult.origin
);
const validationResult = await validateDerivationOrigin({
requestOrigin: issuerOrigin,
derivationOrigin: derivationOriginResult.origin,
resolveCanisterId,
});
if (validationResult.result === "invalid") {
console.error(
`Invalid derivation origin ${derivationOriginResult.origin} for issuer ${issuerOrigin}: ${validationResult.message}`
Expand Down
34 changes: 34 additions & 0 deletions src/frontend/src/test-e2e/alternativeOrigin/endpointFormat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,37 @@ test("Should fetch /.well-known/ii-alternative-origins using the non-raw url", a
await niceDemoAppView.waitForAuthenticated();
});
}, 300_000);

test("Should allow arbitrary URL as derivation origin", async () => {
await runInBrowser(async (browser: WebdriverIO.Browser) => {
const authenticatorId1 = await addVirtualAuthenticator(browser);
await browser.url(II_URL);
const userNumber = await FLOWS.registerNewIdentityWelcomeView(browser);
await FLOWS.addRecoveryMechanismSeedPhrase(browser);
const credentials = await getWebAuthnCredentials(browser, authenticatorId1);
expect(credentials).toHaveLength(1);

const niceDemoAppView = new DemoAppView(browser);
await niceDemoAppView.open(TEST_APP_CANONICAL_URL, II_URL);
await niceDemoAppView.waitForDisplay();
await niceDemoAppView.updateAlternativeOrigins(
`{"alternativeOrigins":["${TEST_APP_CANONICAL_URL}"]}`,
"certified"
);
await niceDemoAppView.setDerivationOrigin(TEST_APP_NICE_URL);
expect(await niceDemoAppView.getPrincipal()).toBe("");
await niceDemoAppView.signin();

const authenticatorId3 = await switchToPopup(browser);
await addWebAuthnCredential(
browser,
authenticatorId3,
credentials[0],
originToRelyingPartyId(II_URL)
);
const authenticateView = new AuthenticateView(browser);
await authenticateView.waitForDisplay();
await authenticateView.pickAnchor(userNumber);
await niceDemoAppView.waitForAuthenticated();
});
}, 300_000);
37 changes: 0 additions & 37 deletions src/frontend/src/test-e2e/alternativeOrigin/ingressFormat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,40 +49,3 @@ test("Should not issue delegation when derivationOrigin is missing from /.well-k
);
});
}, 300_000);

test("Should not issue delegation when derivationOrigin is malformed", async () => {
await runInBrowser(async (browser: WebdriverIO.Browser) => {
const authenticatorId1 = await addVirtualAuthenticator(browser);
await browser.url(II_URL);
await FLOWS.registerNewIdentityWelcomeView(browser);
await FLOWS.addRecoveryMechanismSeedPhrase(browser);
const credentials = await getWebAuthnCredentials(browser, authenticatorId1);
expect(credentials).toHaveLength(1);

const niceDemoAppView = new DemoAppView(browser);
await niceDemoAppView.open(TEST_APP_NICE_URL, II_URL);
await niceDemoAppView.waitForDisplay();
await niceDemoAppView.resetAlternativeOrigins();
await niceDemoAppView.setDerivationOrigin(
"https://some-random-disallowed-url.com"
);
expect(await niceDemoAppView.getPrincipal()).toBe("");
await niceDemoAppView.signin();

const authenticatorId3 = await switchToPopup(browser);
await addWebAuthnCredential(
browser,
authenticatorId3,
credentials[0],
originToRelyingPartyId(II_URL)
);
const errorView = new ErrorView(browser);
await errorView.waitForDisplay();
expect(await errorView.getErrorMessage()).toEqual(
`"https://some-random-disallowed-url.com" is not a valid derivation origin for "${TEST_APP_NICE_URL}"`
);
expect(await errorView.getErrorDetail()).toEqual(
"derivationOrigin does not match regex /^https:\\/\\/([\\w-]+)(?:\\.raw)?\\.(?:ic0\\.app|icp0\\.io)$/"
);
});
}, 300_000);