From f34acd2927ccf877cd3c584e0e7b0d4e3be01ca2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 19 Apr 2026 23:13:45 +0100 Subject: [PATCH] [AI] sync-server: support non-RS256 OpenID ID tokens (#6524) Auto-detect the ID-token signing algorithm from the IdP's discovery metadata, with an optional ACTUAL_OPENID_ID_TOKEN_SIGNED_RESPONSE_ALG override. RS256 is still preferred when advertised, so existing installs are unaffected; IdPs that sign with ECDSA (ES256/ES384/ES512) or other RSA variants now work. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/sync-server/src/accounts/openid.js | 23 ++++++++ .../sync-server/src/accounts/openid.test.ts | 53 +++++++++++++++++++ packages/sync-server/src/load-config.js | 6 +++ packages/sync-server/src/load-config.test.js | 17 ++++++ upcoming-release-notes/7554.md | 6 +++ 5 files changed, 105 insertions(+) create mode 100644 packages/sync-server/src/accounts/openid.test.ts create mode 100644 upcoming-release-notes/7554.md diff --git a/packages/sync-server/src/accounts/openid.js b/packages/sync-server/src/accounts/openid.js index 31fe7a20e0..f3be8d83d8 100644 --- a/packages/sync-server/src/accounts/openid.js +++ b/packages/sync-server/src/accounts/openid.js @@ -64,6 +64,19 @@ export async function bootstrapOpenId(configParameter) { return {}; } +export function pickIdTokenSignedResponseAlg(issuer, configParameter) { + if (configParameter.id_token_signed_response_alg) { + return configParameter.id_token_signed_response_alg; + } + + const supported = issuer?.metadata?.id_token_signing_alg_values_supported; + if (Array.isArray(supported) && supported.length > 0) { + return supported.includes('RS256') ? 'RS256' : supported[0]; + } + + return 'RS256'; +} + async function setupOpenIdClient(configParameter) { const issuer = typeof configParameter.issuer === 'string' @@ -73,6 +86,12 @@ async function setupOpenIdClient(configParameter) { authorization_endpoint: configParameter.issuer.authorization_endpoint, token_endpoint: configParameter.issuer.token_endpoint, userinfo_endpoint: configParameter.issuer.userinfo_endpoint, + ...(Array.isArray( + configParameter.issuer.id_token_signing_alg_values_supported, + ) && { + id_token_signing_alg_values_supported: + configParameter.issuer.id_token_signing_alg_values_supported, + }), }); const client = new issuer.Client({ @@ -83,6 +102,10 @@ async function setupOpenIdClient(configParameter) { configParameter.server_hostname, ).toString(), validate_id_token: true, + id_token_signed_response_alg: pickIdTokenSignedResponseAlg( + issuer, + configParameter, + ), }); return client; diff --git a/packages/sync-server/src/accounts/openid.test.ts b/packages/sync-server/src/accounts/openid.test.ts new file mode 100644 index 0000000000..7e367d6f70 --- /dev/null +++ b/packages/sync-server/src/accounts/openid.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, it } from 'vitest'; + +import { pickIdTokenSignedResponseAlg } from './openid'; + +describe('pickIdTokenSignedResponseAlg', () => { + it('returns the explicit override when provided, even if discovery suggests otherwise', () => { + const issuer = { + metadata: { id_token_signing_alg_values_supported: ['RS256', 'ES384'] }, + }; + expect( + pickIdTokenSignedResponseAlg(issuer, { + id_token_signed_response_alg: 'ES512', + }), + ).toBe('ES512'); + }); + + it('prefers RS256 when it is among the advertised algorithms (backwards-compat)', () => { + const issuer = { + metadata: { id_token_signing_alg_values_supported: ['ES384', 'RS256'] }, + }; + expect(pickIdTokenSignedResponseAlg(issuer, {})).toBe('RS256'); + }); + + it('falls back to the first advertised algorithm when RS256 is not offered', () => { + const issuer = { + metadata: { id_token_signing_alg_values_supported: ['ES384', 'ES256'] }, + }; + expect(pickIdTokenSignedResponseAlg(issuer, {})).toBe('ES384'); + }); + + it('uses a lone ECDSA algorithm (the bug-report case)', () => { + const issuer = { + metadata: { id_token_signing_alg_values_supported: ['ES384'] }, + }; + expect(pickIdTokenSignedResponseAlg(issuer, {})).toBe('ES384'); + }); + + it('defaults to RS256 when the issuer exposes no signing metadata', () => { + expect(pickIdTokenSignedResponseAlg({ metadata: {} }, {})).toBe('RS256'); + expect(pickIdTokenSignedResponseAlg(undefined, {})).toBe('RS256'); + }); + + it('treats an empty override as "auto-detect"', () => { + const issuer = { + metadata: { id_token_signing_alg_values_supported: ['ES384'] }, + }; + expect( + pickIdTokenSignedResponseAlg(issuer, { + id_token_signed_response_alg: '', + }), + ).toBe('ES384'); + }); +}); diff --git a/packages/sync-server/src/load-config.js b/packages/sync-server/src/load-config.js index 82183ce310..9f5745093b 100644 --- a/packages/sync-server/src/load-config.js +++ b/packages/sync-server/src/load-config.js @@ -255,6 +255,12 @@ const configSchema = convict({ default: 'openid', env: 'ACTUAL_OPENID_AUTH_METHOD', }, + id_token_signed_response_alg: { + doc: 'Algorithm used by the OpenID provider to sign ID tokens (e.g. RS256, ES256, ES384). Auto-detected from discovery metadata when empty.', + format: String, + default: '', + env: 'ACTUAL_OPENID_ID_TOKEN_SIGNED_RESPONSE_ALG', + }, }, token_expiration: { diff --git a/packages/sync-server/src/load-config.test.js b/packages/sync-server/src/load-config.test.js index a1dc7b13f8..5bd111b56a 100644 --- a/packages/sync-server/src/load-config.test.js +++ b/packages/sync-server/src/load-config.test.js @@ -138,4 +138,21 @@ describe('config schema', () => { authorizationEndpoint, ); }); + + it('parses the ID token signing algorithm override from the environment', () => { + process.env.TEST_OPENID_ID_TOKEN_SIGNED_RESPONSE_ALG = 'ES384'; + + const testSchema = convict({ + openId: { + id_token_signed_response_alg: { + doc: 'Algorithm used by the OpenID provider to sign ID tokens.', + format: String, + default: '', + env: 'TEST_OPENID_ID_TOKEN_SIGNED_RESPONSE_ALG', + }, + }, + }); + expect(() => testSchema.validate()).not.toThrow(); + expect(testSchema.get('openId.id_token_signed_response_alg')).toBe('ES384'); + }); }); diff --git a/upcoming-release-notes/7554.md b/upcoming-release-notes/7554.md new file mode 100644 index 0000000000..044ad08624 --- /dev/null +++ b/upcoming-release-notes/7554.md @@ -0,0 +1,6 @@ +--- +category: Bugfixes +authors: [MatissJanis] +--- + +Sync server: accept non-RS256 OpenID ID tokens. The ID-token signing algorithm is now auto-detected from the provider's discovery metadata (with an optional `ACTUAL_OPENID_ID_TOKEN_SIGNED_RESPONSE_ALG` override), fixing login against IdPs that sign with ECDSA (ES256/ES384/ES512) or other RSA variants.