Skip to content

Commit 569f552

Browse files
authored
Stop sending scope on the authorization code token request (#579)
1 parent 33f32cc commit 569f552

2 files changed

Lines changed: 100 additions & 1 deletion

File tree

plugins/auth-oauth2/src/fetchAccessToken.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,13 @@ export async function fetchAccessToken(
3131
],
3232
};
3333

34-
if (scope) httpRequest.body?.form.push({ name: "scope", value: scope });
34+
// RFC 6749 §4.1.3 doesn't define scope for the authorization code token
35+
// request, so strict servers (OpenIddict) reject it outright. Scope belongs on
36+
// the authorize request, which already sends it. Every other grant does define
37+
// it: §4.3.2 password, §4.4.2 client credentials, §6 refresh.
38+
if (scope && grantType !== "authorization_code") {
39+
httpRequest.body?.form.push({ name: "scope", value: scope });
40+
}
3541
if (audience) httpRequest.body?.form.push({ name: "audience", value: audience });
3642

3743
if ("clientAssertion" in args) {
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import type { HttpRequest } from "@yaakapp/api";
2+
import { describe, expect, test } from "vite-plus/test";
3+
import { fetchAccessToken } from "../src/fetchAccessToken";
4+
5+
/**
6+
* Captures the request handed to ctx.httpRequest.send so tests can assert on the
7+
* form body, and replies with a minimal successful token response.
8+
*/
9+
function createMockContext() {
10+
const sent: Partial<HttpRequest>[] = [];
11+
12+
const ctx = {
13+
httpRequest: {
14+
async send({ httpRequest }: { httpRequest: Partial<HttpRequest> }) {
15+
sent.push(httpRequest);
16+
return {
17+
httpResponse: { status: 200, error: null },
18+
body: {
19+
async text() {
20+
return JSON.stringify({ access_token: "token-123" });
21+
},
22+
},
23+
};
24+
},
25+
},
26+
} as never;
27+
28+
return { ctx, sent };
29+
}
30+
31+
function formNames(httpRequest: Partial<HttpRequest>) {
32+
return (httpRequest.body?.form ?? []).map((p: { name: string }) => p.name);
33+
}
34+
35+
function formValue(httpRequest: Partial<HttpRequest>, name: string) {
36+
return (httpRequest.body?.form ?? []).find((p: { name: string }) => p.name === name)?.value;
37+
}
38+
39+
const baseArgs = {
40+
clientId: "client-123",
41+
accessTokenUrl: "https://auth.example.com/token",
42+
scope: "openid profile",
43+
audience: null,
44+
clientSecret: "secret",
45+
credentialsInBody: true,
46+
params: [],
47+
};
48+
49+
describe("fetchAccessToken scope handling", () => {
50+
test("omits scope for the authorization code grant", async () => {
51+
const { ctx, sent } = createMockContext();
52+
53+
await fetchAccessToken(ctx, {
54+
...baseArgs,
55+
grantType: "authorization_code",
56+
params: [{ name: "code", value: "abc" }],
57+
});
58+
59+
expect(formNames(sent[0]!)).not.toContain("scope");
60+
// The rest of the request is untouched
61+
expect(formValue(sent[0]!, "grant_type")).toBe("authorization_code");
62+
expect(formValue(sent[0]!, "code")).toBe("abc");
63+
});
64+
65+
test("sends scope for the client credentials grant", async () => {
66+
const { ctx, sent } = createMockContext();
67+
68+
await fetchAccessToken(ctx, { ...baseArgs, grantType: "client_credentials" });
69+
70+
expect(formValue(sent[0]!, "scope")).toBe("openid profile");
71+
});
72+
73+
test("sends scope for the password grant", async () => {
74+
const { ctx, sent } = createMockContext();
75+
76+
await fetchAccessToken(ctx, { ...baseArgs, grantType: "password" });
77+
78+
expect(formValue(sent[0]!, "scope")).toBe("openid profile");
79+
});
80+
81+
test("still sends audience for the authorization code grant", async () => {
82+
const { ctx, sent } = createMockContext();
83+
84+
await fetchAccessToken(ctx, {
85+
...baseArgs,
86+
grantType: "authorization_code",
87+
audience: "https://api.example.com",
88+
});
89+
90+
expect(formValue(sent[0]!, "audience")).toBe("https://api.example.com");
91+
expect(formNames(sent[0]!)).not.toContain("scope");
92+
});
93+
});

0 commit comments

Comments
 (0)