You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-07-31 15:24:23 +03:00
OIDC improvements in prep of OIDC-QR reciprocation (#4149)
* Add `device_authorization_endpoint` field to OIDC issuer well-known metadata Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Allow `validateIdToken` to skip handling nonce when none is present Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Tweak registerOidcClient to check OIDC grant_types_supported before registration Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
committed by
GitHub
parent
0ff0093380
commit
5cdd524da7
@ -44,6 +44,7 @@ export const mockOpenIdConfiguration = (issuer = "https://auth.org/"): Validated
|
|||||||
token_endpoint: issuer + "token",
|
token_endpoint: issuer + "token",
|
||||||
authorization_endpoint: issuer + "auth",
|
authorization_endpoint: issuer + "auth",
|
||||||
registration_endpoint: issuer + "registration",
|
registration_endpoint: issuer + "registration",
|
||||||
|
device_authorization_endpoint: issuer + "device",
|
||||||
jwks_uri: issuer + "jwks",
|
jwks_uri: issuer + "jwks",
|
||||||
response_types_supported: ["code"],
|
response_types_supported: ["code"],
|
||||||
grant_types_supported: ["authorization_code", "refresh_token"],
|
grant_types_supported: ["authorization_code", "refresh_token"],
|
||||||
|
@ -90,4 +90,31 @@ describe("registerOidcClient()", () => {
|
|||||||
OidcError.DynamicRegistrationInvalid,
|
OidcError.DynamicRegistrationInvalid,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should throw when required endpoints are unavailable", async () => {
|
||||||
|
await expect(() =>
|
||||||
|
registerOidcClient(
|
||||||
|
{
|
||||||
|
...delegatedAuthConfig,
|
||||||
|
registrationEndpoint: undefined,
|
||||||
|
},
|
||||||
|
metadata,
|
||||||
|
),
|
||||||
|
).rejects.toThrow(OidcError.DynamicRegistrationNotSupported);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw when required scopes are unavailable", async () => {
|
||||||
|
await expect(() =>
|
||||||
|
registerOidcClient(
|
||||||
|
{
|
||||||
|
...delegatedAuthConfig,
|
||||||
|
metadata: {
|
||||||
|
...delegatedAuthConfig.metadata,
|
||||||
|
grant_types_supported: [delegatedAuthConfig.metadata.grant_types_supported[0]],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
metadata,
|
||||||
|
),
|
||||||
|
).rejects.toThrow(OidcError.DynamicRegistrationNotSupported);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
24
src/@types/oidc-client-ts.d.ts
vendored
Normal file
24
src/@types/oidc-client-ts.d.ts
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "oidc-client-ts";
|
||||||
|
|
||||||
|
declare module "oidc-client-ts" {
|
||||||
|
interface OidcMetadata {
|
||||||
|
// Add the missing device_authorization_endpoint field to the OidcMetadata interface
|
||||||
|
device_authorization_endpoint?: string;
|
||||||
|
}
|
||||||
|
}
|
@ -50,23 +50,31 @@ interface OidcRegistrationRequestBody {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make the client registration request
|
* Attempts dynamic registration against the configured registration endpoint
|
||||||
* @param registrationEndpoint - URL as returned from issuer ./well-known/openid-configuration
|
* @param delegatedAuthConfig - Auth config from {@link discoverAndValidateOIDCIssuerWellKnown}
|
||||||
* @param clientMetadata - registration metadata
|
* @param clientMetadata - The metadata for the client which to register
|
||||||
* @returns resolves to the registered client id when registration is successful
|
* @returns Promise<string> resolved with registered clientId
|
||||||
* @throws An `Error` with `message` set to an entry in {@link OidcError},
|
* @throws when registration is not supported, on failed request or invalid response
|
||||||
* when the registration request fails, or the response is invalid.
|
|
||||||
*/
|
*/
|
||||||
const doRegistration = async (
|
export const registerOidcClient = async (
|
||||||
registrationEndpoint: string,
|
delegatedAuthConfig: OidcClientConfig,
|
||||||
clientMetadata: OidcRegistrationClientMetadata,
|
clientMetadata: OidcRegistrationClientMetadata,
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
|
if (!delegatedAuthConfig.registrationEndpoint) {
|
||||||
|
throw new Error(OidcError.DynamicRegistrationNotSupported);
|
||||||
|
}
|
||||||
|
|
||||||
|
const grantTypes: NonEmptyArray<string> = ["authorization_code", "refresh_token"];
|
||||||
|
if (grantTypes.some((scope) => !delegatedAuthConfig.metadata.grant_types_supported.includes(scope))) {
|
||||||
|
throw new Error(OidcError.DynamicRegistrationNotSupported);
|
||||||
|
}
|
||||||
|
|
||||||
// https://openid.net/specs/openid-connect-registration-1_0.html
|
// https://openid.net/specs/openid-connect-registration-1_0.html
|
||||||
const metadata: OidcRegistrationRequestBody = {
|
const metadata: OidcRegistrationRequestBody = {
|
||||||
client_name: clientMetadata.clientName,
|
client_name: clientMetadata.clientName,
|
||||||
client_uri: clientMetadata.clientUri,
|
client_uri: clientMetadata.clientUri,
|
||||||
response_types: ["code"],
|
response_types: ["code"],
|
||||||
grant_types: ["authorization_code", "refresh_token"],
|
grant_types: grantTypes,
|
||||||
redirect_uris: clientMetadata.redirectUris,
|
redirect_uris: clientMetadata.redirectUris,
|
||||||
id_token_signed_response_alg: "RS256",
|
id_token_signed_response_alg: "RS256",
|
||||||
token_endpoint_auth_method: "none",
|
token_endpoint_auth_method: "none",
|
||||||
@ -82,7 +90,7 @@ const doRegistration = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(registrationEndpoint, {
|
const response = await fetch(delegatedAuthConfig.registrationEndpoint, {
|
||||||
method: Method.Post,
|
method: Method.Post,
|
||||||
headers,
|
headers,
|
||||||
body: JSON.stringify(metadata),
|
body: JSON.stringify(metadata),
|
||||||
@ -108,20 +116,3 @@ const doRegistration = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts dynamic registration against the configured registration endpoint
|
|
||||||
* @param delegatedAuthConfig - Auth config from {@link discoverAndValidateOIDCIssuerWellKnown}
|
|
||||||
* @param clientMetadata - The metadata for the client which to register
|
|
||||||
* @returns Promise<string> resolved with registered clientId
|
|
||||||
* @throws when registration is not supported, on failed request or invalid response
|
|
||||||
*/
|
|
||||||
export const registerOidcClient = async (
|
|
||||||
delegatedAuthConfig: OidcClientConfig,
|
|
||||||
clientMetadata: OidcRegistrationClientMetadata,
|
|
||||||
): Promise<string> => {
|
|
||||||
if (!delegatedAuthConfig.registrationEndpoint) {
|
|
||||||
throw new Error(OidcError.DynamicRegistrationNotSupported);
|
|
||||||
}
|
|
||||||
return doRegistration(delegatedAuthConfig.registrationEndpoint, clientMetadata);
|
|
||||||
};
|
|
||||||
|
@ -83,6 +83,7 @@ export const validateOIDCIssuerWellKnown = (wellKnown: unknown): ValidatedIssuer
|
|||||||
requiredStringProperty(wellKnown, "revocation_endpoint"),
|
requiredStringProperty(wellKnown, "revocation_endpoint"),
|
||||||
optionalStringProperty(wellKnown, "registration_endpoint"),
|
optionalStringProperty(wellKnown, "registration_endpoint"),
|
||||||
optionalStringProperty(wellKnown, "account_management_uri"),
|
optionalStringProperty(wellKnown, "account_management_uri"),
|
||||||
|
optionalStringProperty(wellKnown, "device_authorization_endpoint"),
|
||||||
optionalStringArrayProperty(wellKnown, "account_management_actions_supported"),
|
optionalStringArrayProperty(wellKnown, "account_management_actions_supported"),
|
||||||
requiredArrayValue(wellKnown, "response_types_supported", "code"),
|
requiredArrayValue(wellKnown, "response_types_supported", "code"),
|
||||||
requiredArrayValue(wellKnown, "grant_types_supported", "authorization_code"),
|
requiredArrayValue(wellKnown, "grant_types_supported", "authorization_code"),
|
||||||
@ -118,6 +119,7 @@ export type ValidatedIssuerMetadata = Partial<OidcMetadata> &
|
|||||||
| "response_types_supported"
|
| "response_types_supported"
|
||||||
| "grant_types_supported"
|
| "grant_types_supported"
|
||||||
| "code_challenge_methods_supported"
|
| "code_challenge_methods_supported"
|
||||||
|
| "device_authorization_endpoint"
|
||||||
> & {
|
> & {
|
||||||
// MSC2965 extensions to the OIDC spec
|
// MSC2965 extensions to the OIDC spec
|
||||||
account_management_uri?: string;
|
account_management_uri?: string;
|
||||||
@ -176,7 +178,12 @@ const decodeIdToken = (token: string): IdTokenClaims => {
|
|||||||
* @param nonce - nonce used in the authentication request
|
* @param nonce - nonce used in the authentication request
|
||||||
* @throws when id token is invalid
|
* @throws when id token is invalid
|
||||||
*/
|
*/
|
||||||
export const validateIdToken = (idToken: string | undefined, issuer: string, clientId: string, nonce: string): void => {
|
export const validateIdToken = (
|
||||||
|
idToken: string | undefined,
|
||||||
|
issuer: string,
|
||||||
|
clientId: string,
|
||||||
|
nonce: string | undefined,
|
||||||
|
): void => {
|
||||||
try {
|
try {
|
||||||
if (!idToken) {
|
if (!idToken) {
|
||||||
throw new Error("No ID token");
|
throw new Error("No ID token");
|
||||||
@ -201,7 +208,7 @@ export const validateIdToken = (idToken: string | undefined, issuer: string, cli
|
|||||||
* If a nonce value was sent in the Authentication Request, a nonce Claim MUST be present and its value checked
|
* If a nonce value was sent in the Authentication Request, a nonce Claim MUST be present and its value checked
|
||||||
* to verify that it is the same value as the one that was sent in the Authentication Request.
|
* to verify that it is the same value as the one that was sent in the Authentication Request.
|
||||||
*/
|
*/
|
||||||
if (claims.nonce !== nonce) {
|
if (nonce !== undefined && claims.nonce !== nonce) {
|
||||||
throw new Error("Invalid nonce");
|
throw new Error("Invalid nonce");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user