feat(auth): add OIDC state parameter

Signed-off-by: Ivan Li <ivanli2048@gmail.com>
This commit is contained in:
Ivan Li 2024-10-14 13:12:30 +08:00 committed by Erik Michelson
parent 8b6bedab39
commit 19f4baf79b
3 changed files with 26 additions and 1 deletions

View file

@ -41,12 +41,15 @@ export class OidcController {
@Param('oidcIdentifier') oidcIdentifier: string, @Param('oidcIdentifier') oidcIdentifier: string,
): { url: string } { ): { url: string } {
const code = this.oidcService.generateCode(); const code = this.oidcService.generateCode();
const state = this.oidcService.generateState();
request.session.oidcLoginCode = code; request.session.oidcLoginCode = code;
request.session.oidcLoginState = state;
request.session.authProviderType = ProviderType.OIDC; request.session.authProviderType = ProviderType.OIDC;
request.session.authProviderIdentifier = oidcIdentifier; request.session.authProviderIdentifier = oidcIdentifier;
const authorizationUrl = this.oidcService.getAuthorizationUrl( const authorizationUrl = this.oidcService.getAuthorizationUrl(
oidcIdentifier, oidcIdentifier,
code, code,
state,
); );
return { url: authorizationUrl }; return { url: authorizationUrl };
} }

View file

@ -119,14 +119,28 @@ export class OidcService {
return generators.codeVerifier(); 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. * Generates the authorization URL for the given OIDC identifier and code.
* *
* @param {string} oidcIdentifier The identifier of the OIDC configuration * @param {string} oidcIdentifier The identifier of the OIDC configuration
* @param {string} code The code verifier generated for the login * @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 * @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); const clientConfig = this.clientConfigs.get(oidcIdentifier);
if (!clientConfig) { if (!clientConfig) {
throw new NotFoundException( throw new NotFoundException(
@ -139,6 +153,7 @@ export class OidcService {
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
code_challenge: generators.codeChallenge(code), code_challenge: generators.codeChallenge(code),
code_challenge_method: 'S256', code_challenge_method: 'S256',
state,
/* eslint-enable @typescript-eslint/naming-convention */ /* eslint-enable @typescript-eslint/naming-convention */
}); });
} }
@ -166,15 +181,18 @@ export class OidcService {
const oidcConfig = clientConfig.config; const oidcConfig = clientConfig.config;
const params = client.callbackParams(request); const params = client.callbackParams(request);
const code = request.session.oidcLoginCode; const code = request.session.oidcLoginCode;
const state = request.session.oidcLoginState;
const isAutodiscovered = clientConfig.config.authorizeUrl === undefined; const isAutodiscovered = clientConfig.config.authorizeUrl === undefined;
const tokenSet = isAutodiscovered const tokenSet = isAutodiscovered
? await client.callback(clientConfig.redirectUri, params, { ? await client.callback(clientConfig.redirectUri, params, {
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
code_verifier: code, code_verifier: code,
state,
}) })
: await client.oauthCallback(clientConfig.redirectUri, params, { : await client.oauthCallback(clientConfig.redirectUri, params, {
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
code_verifier: code, code_verifier: code,
state,
}); });
request.session.oidcIdToken = tokenSet.id_token; request.session.oidcIdToken = tokenSet.id_token;
@ -214,6 +232,7 @@ export class OidcService {
request.session.newUserData = newUserData; request.session.newUserData = newUserData;
// Cleanup: The code isn't necessary anymore // Cleanup: The code isn't necessary anymore
request.session.oidcLoginCode = undefined; request.session.oidcLoginCode = undefined;
request.session.oidcLoginState = undefined;
return newUserData; return newUserData;
} }

View file

@ -43,6 +43,9 @@ export interface SessionState {
/** The (random) OIDC code for verifying that OIDC responses match the OIDC requests */ /** The (random) OIDC code for verifying that OIDC responses match the OIDC requests */
oidcLoginCode?: string; 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 */ /** The user id as provided from the external auth provider, required for matching to a HedgeDoc identity */
providerUserId?: string; providerUserId?: string;