From ae55573b68c8057136e1d7bc1c89dc155fd35f53 Mon Sep 17 00:00:00 2001 From: David Villalba Flores Date: Thu, 15 Oct 2026 11:25:32 +0200 Subject: [PATCH] fix(auth): request user:email scope for GitHub OAuth sign-in The authorize URL never set a scope, so the OAuth token GitHub issued had no access to /user/emails, making the identity exchange fail with "GitHub identity or installation access could not be verified" even for a correctly configured app and valid credentials. Co-Authored-By: Claude Sonnet 5 --- services/api/src/auth/identity-provider.ts | 1 + services/api/test/identity-provider.test.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/services/api/src/auth/identity-provider.ts b/services/api/src/auth/identity-provider.ts index 747c47a0..f50e8e48 100644 --- a/services/api/src/auth/identity-provider.ts +++ b/services/api/src/auth/identity-provider.ts @@ -46,6 +46,7 @@ export class ExternalIdentityProvider { url.search = new URLSearchParams({ client_id: requiredConfig(this.config.githubOauthClientId), redirect_uri: callbackUrl(this.config), + scope: "user:email", state: transaction.state, code_challenge: challenge, code_challenge_method: "S256", diff --git a/services/api/test/identity-provider.test.ts b/services/api/test/identity-provider.test.ts index 60d5c2a8..5471eb77 100644 --- a/services/api/test/identity-provider.test.ts +++ b/services/api/test/identity-provider.test.ts @@ -65,6 +65,7 @@ describe("GitHub external identity integration", () => { const authorization = new URL(await provider.authorizationUrl(transaction)); expect(authorization.searchParams.get("state")).toBe("state"); expect(authorization.searchParams.get("code_challenge_method")).toBe("S256"); + expect(authorization.searchParams.get("scope")).toBe("user:email"); await expect(provider.exchange("code", transaction)).resolves.toEqual({ provider: "github", githubUserId: "123",