diff --git a/backend/src/api/private/auth/oidc/oidc.controller.ts b/backend/src/api/private/auth/oidc/oidc.controller.ts index af92314eb..ce7434054 100644 --- a/backend/src/api/private/auth/oidc/oidc.controller.ts +++ b/backend/src/api/private/auth/oidc/oidc.controller.ts @@ -41,12 +41,15 @@ export class OidcController { @Param('oidcIdentifier') oidcIdentifier: string, ): { url: string } { const code = this.oidcService.generateCode(); + const state = this.oidcService.generateState(); request.session.oidcLoginCode = code; + request.session.oidcLoginState = state; request.session.authProviderType = ProviderType.OIDC; request.session.authProviderIdentifier = oidcIdentifier; const authorizationUrl = this.oidcService.getAuthorizationUrl( oidcIdentifier, code, + state, ); return { url: authorizationUrl }; } diff --git a/backend/src/identity/oidc/oidc.service.ts b/backend/src/identity/oidc/oidc.service.ts index 39e0377c4..26a04422d 100644 --- a/backend/src/identity/oidc/oidc.service.ts +++ b/backend/src/identity/oidc/oidc.service.ts @@ -119,14 +119,28 @@ export class OidcService { return generators.codeVerifier(); } + /** + * Generates a random state for the OIDC login. + * + * @returns {string} The generated state. + */ + generateState(): string { + return generators.state(); + } + /** * Generates the authorization URL for the given OIDC identifier and code. * * @param {string} oidcIdentifier The identifier of the OIDC configuration * @param {string} code The code verifier generated for the login + * @param {string} state The state generated for the login * @returns {string} The generated authorization URL */ - getAuthorizationUrl(oidcIdentifier: string, code: string): string { + getAuthorizationUrl( + oidcIdentifier: string, + code: string, + state: string, + ): string { const clientConfig = this.clientConfigs.get(oidcIdentifier); if (!clientConfig) { throw new NotFoundException( @@ -139,6 +153,7 @@ export class OidcService { /* eslint-disable @typescript-eslint/naming-convention */ code_challenge: generators.codeChallenge(code), code_challenge_method: 'S256', + state, /* eslint-enable @typescript-eslint/naming-convention */ }); } @@ -166,15 +181,18 @@ export class OidcService { const oidcConfig = clientConfig.config; const params = client.callbackParams(request); const code = request.session.oidcLoginCode; + const state = request.session.oidcLoginState; const isAutodiscovered = clientConfig.config.authorizeUrl === undefined; const tokenSet = isAutodiscovered ? await client.callback(clientConfig.redirectUri, params, { // eslint-disable-next-line @typescript-eslint/naming-convention code_verifier: code, + state, }) : await client.oauthCallback(clientConfig.redirectUri, params, { // eslint-disable-next-line @typescript-eslint/naming-convention code_verifier: code, + state, }); request.session.oidcIdToken = tokenSet.id_token; @@ -214,6 +232,7 @@ export class OidcService { request.session.newUserData = newUserData; // Cleanup: The code isn't necessary anymore request.session.oidcLoginCode = undefined; + request.session.oidcLoginState = undefined; return newUserData; } diff --git a/backend/src/sessions/session.service.ts b/backend/src/sessions/session.service.ts index 71c683a23..1a73cb474 100644 --- a/backend/src/sessions/session.service.ts +++ b/backend/src/sessions/session.service.ts @@ -43,6 +43,9 @@ export interface SessionState { /** The (random) OIDC code for verifying that OIDC responses match the OIDC requests */ oidcLoginCode?: string; + /** The (random) OIDC state for verifying that OIDC responses match the OIDC requests */ + oidcLoginState?: string; + /** The user id as provided from the external auth provider, required for matching to a HedgeDoc identity */ providerUserId?: string;