Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code 4595ef980c fix(auth): add iss parameter to authorization response and pass scope/resource through OAuth flow
https://sonarly.com/issue/34930?type=bug

The OAuth authorization flow advertises `authorization_response_iss_parameter_supported: true` in discovery metadata but never includes the `iss` parameter in the authorization response redirect. Additionally, `scope` and `resource` URL parameters from the authorization request are not captured or forwarded through the flow. Strict OAuth 2.1 clients like ChatGPT reject the incomplete authorization response.

Fix: Three surgical changes to complete the MCP OAuth flow for strict clients like ChatGPT:

1. **`auth.service.ts`** — Added `iss` (issuer) parameter to the authorization response redirect URL per RFC 9207. The discovery metadata already advertises `authorization_response_iss_parameter_supported: true`, so strict OAuth 2.1 clients expect this parameter in the callback. Without it, clients like ChatGPT reject the authorization response as a potential mix-up attack. Uses `this.twentyConfigService.get('SERVER_URL')` — the same pattern already used in `oauth.service.ts:478` for token introspection.

2. **`authorizeApp.ts`** — Added `$scope: String` variable to the GraphQL mutation definition so the frontend can forward the scope parameter from the authorization URL to the backend.

3. **`Authorize.tsx`** — Reads the `scope` query parameter from the authorization URL (which MCP clients include per spec) and passes it through to the `authorizeApp` mutation. This allows proper scope negotiation instead of silently defaulting to all scopes.

**Post-merge action required:** Run `npx nx run twenty-front:graphql:generate` to regenerate the typed GraphQL client (`generated-metadata/graphql.ts`) which will pick up the new `scope` variable in the `AuthorizeAppMutationVariables` type.

**Note:** The `resource` parameter (RFC 9396 / RFC 9728) is not addressed in this PR. Full resource indicator support would require storing `resource` in the auth code context and validating it during token exchange — a larger change that should be handled in a follow-up.
2026-05-05 16:14:49 +00:00
3 changed files with 9 additions and 0 deletions
@@ -6,12 +6,14 @@ export const AUTHORIZE_APP = gql`
$codeChallenge: String
$redirectUrl: String!
$state: String
$scope: String
) {
authorizeApp(
clientId: $clientId
codeChallenge: $codeChallenge
redirectUrl: $redirectUrl
state: $state
scope: $scope
) {
redirectUrl
}
@@ -106,6 +106,7 @@ export const Authorize = () => {
const redirectUrl =
searchParam.get('redirect_uri') ?? searchParam.get('redirectUrl');
const state = searchParam.get('state');
const scope = searchParam.get('scope');
const {
data,
@@ -142,6 +143,7 @@ export const Authorize = () => {
codeChallenge: codeChallenge ?? undefined,
redirectUrl,
state: state ?? undefined,
scope: scope ?? undefined,
},
onCompleted: (responseData) => {
redirect(responseData.authorizeApp.redirectUrl);
@@ -645,6 +645,11 @@ export class AuthService {
);
}
// RFC 9207: include `iss` in authorization response to prevent mix-up attacks
const issuer = this.twentyConfigService.get('SERVER_URL');
redirectUriValidation.parsed.searchParams.set('iss', issuer);
return { redirectUrl: redirectUriValidation.parsed.toString() };
}