Skip to content

Commit d90cbf7

Browse files
committed
feat(backend:auth): add toggle for security.supportPKCE in OIDC provider
1 parent dcf57c4 commit d90cbf7

File tree

4 files changed

+37
-7
lines changed

4 files changed

+37
-7
lines changed

backend/src/authentication/providers/oidc/auth-oidc.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ export class AuthProviderOIDCSecurityConfig {
2020
@Matches(/\bopenid\b/, { message: 'OIDC scope must include "openid"' })
2121
scope = 'openid email profile'
2222

23+
@IsOptional()
24+
@IsBoolean()
25+
supportPKCE? = true
26+
2327
@Transform(({ value }) => value || OAuthTokenEndpoint.ClientSecretBasic)
2428
@IsEnum(OAuthTokenEndpoint)
2529
tokenEndpointAuthMethod: OAuthTokenEndpoint = OAuthTokenEndpoint.ClientSecretBasic

backend/src/authentication/providers/oidc/auth-provider-oidc.service.spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ jest.mock('../../../configuration/config.environment', () => ({
2525
redirectUri: 'https://api.example.test/auth/oidc/callback',
2626
security: {
2727
scope: 'openid profile email',
28+
supportPKCE: true,
2829
tokenSigningAlg: 'RS256',
2930
userInfoSigningAlg: 'RS256',
3031
tokenEndpointAuthMethod: 'client_secret_basic',
@@ -163,6 +164,23 @@ describe(AuthProviderOIDC.name, () => {
163164
expect(url.searchParams.get('client_id')).toBe('client-id')
164165
})
165166

167+
it('does not use PKCE when supportPKCE is false', async () => {
168+
;(service as any).oidcConfig.security.supportPKCE = false
169+
jest.spyOn(service, 'getConfig').mockResolvedValue(makeConfig(true) as any)
170+
;(randomState as jest.Mock).mockReturnValue('state-1')
171+
;(randomNonce as jest.Mock).mockReturnValue('nonce-1')
172+
const reply = makeReply()
173+
174+
const authUrl = await service.getAuthorizationUrl(reply as any)
175+
176+
expect(randomPKCECodeVerifier).not.toHaveBeenCalled()
177+
expect(calculatePKCECodeChallenge).not.toHaveBeenCalled()
178+
expect(reply.setCookie).not.toHaveBeenCalledWith(OAuthCookie.CodeVerifier, expect.anything(), expect.any(Object))
179+
const url = new URL(authUrl)
180+
expect(url.searchParams.get('code_challenge')).toBeNull()
181+
;(service as any).oidcConfig.security.supportPKCE = true
182+
})
183+
166184
it('handles callback success and clears cookies', async () => {
167185
const config = makeConfig(true)
168186
jest.spyOn(service, 'getConfig').mockResolvedValue(config as any)

backend/src/authentication/providers/oidc/auth-provider-oidc.service.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ export class AuthProviderOIDC implements AuthProvider {
8181
const state = randomState()
8282
const nonce = randomNonce()
8383

84-
const supportsPKCE = config.serverMetadata().supportsPKCE()
85-
const codeVerifier = supportsPKCE ? randomPKCECodeVerifier() : undefined
84+
const isPKCEEnabled = this.isPKCEEnabled(config)
85+
const codeVerifier = isPKCEEnabled ? randomPKCECodeVerifier() : undefined
8686

8787
const authUrl = new URL(config.serverMetadata().authorization_endpoint!)
8888
authUrl.searchParams.set('client_id', this.oidcConfig.clientId!)
@@ -91,7 +91,7 @@ export class AuthProviderOIDC implements AuthProvider {
9191
authUrl.searchParams.set('scope', this.oidcConfig.security.scope)
9292
authUrl.searchParams.set('state', state)
9393
authUrl.searchParams.set('nonce', nonce)
94-
if (supportsPKCE) {
94+
if (isPKCEEnabled) {
9595
const codeChallenge = await calculatePKCECodeChallenge(codeVerifier!)
9696
authUrl.searchParams.set('code_challenge', codeChallenge)
9797
authUrl.searchParams.set('code_challenge_method', 'S256')
@@ -108,15 +108,15 @@ export class AuthProviderOIDC implements AuthProvider {
108108
// Store state, nonce, and codeVerifier in httpOnly cookies (expires in 10 minutes)
109109
res.setCookie(OAuthCookie.State, state, OAuthCookieSettings)
110110
res.setCookie(OAuthCookie.Nonce, nonce, OAuthCookieSettings)
111-
if (supportsPKCE) {
111+
if (isPKCEEnabled) {
112112
res.setCookie(OAuthCookie.CodeVerifier, codeVerifier, OAuthCookieSettings)
113113
}
114114
return authUrl.toString()
115115
}
116116

117117
async handleCallback(req: FastifyRequest, res: FastifyReply, query: Record<string, string>): Promise<UserModel> {
118118
const config = await this.getConfig()
119-
const supportsPKCE = config.serverMetadata().supportsPKCE()
119+
const isPKCEEnabled = this.isPKCEEnabled(config)
120120
const [expectedState, expectedNonce, codeVerifier] = [
121121
req.cookies[OAuthCookie.State],
122122
req.cookies[OAuthCookie.Nonce],
@@ -128,11 +128,11 @@ export class AuthProviderOIDC implements AuthProvider {
128128
throw new HttpException('OAuth state is missing', HttpStatus.BAD_REQUEST)
129129
}
130130

131-
if (supportsPKCE && !codeVerifier?.length) {
131+
if (isPKCEEnabled && !codeVerifier?.length) {
132132
throw new HttpException('OAuth code verifier is missing', HttpStatus.BAD_REQUEST)
133133
}
134134

135-
const pkceCodeVerifier = supportsPKCE ? codeVerifier : undefined
135+
const pkceCodeVerifier = isPKCEEnabled ? codeVerifier : undefined
136136
const callbackParams = new URLSearchParams(query)
137137

138138
// Get Desktop Port if defined
@@ -275,6 +275,10 @@ export class AuthProviderOIDC implements AuthProvider {
275275
}
276276
}
277277

278+
private isPKCEEnabled(config: Configuration): boolean {
279+
return (this.oidcConfig.security.supportPKCE ?? true) && config.serverMetadata().supportsPKCE()
280+
}
281+
278282
private async processUserInfo(userInfo: UserInfoResponse, ip?: string): Promise<UserModel> {
279283
// Extract user information
280284
const { login, email } = this.extractLoginAndEmail(userInfo)

environment/environment.dist.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,10 @@ auth:
258258
# Common scopes: openid (required), email, profile, groups, roles
259259
# default: `openid email profile`
260260
scope: openid email profile
261+
# supportPKCE: Enable PKCE (Proof Key for Code Exchange) in the authorization code flow.
262+
# When true, PKCE is used if supported by the OIDC provider.
263+
# default: true
264+
supportPKCE: true
261265
# OAuth 2.0 / OIDC client authentication method used at the token endpoint.
262266
# Possible values:
263267
# - client_secret_basic (DEFAULT): HTTP Basic auth using client_id and client_secret.

0 commit comments

Comments
 (0)