mirror of
https://github.com/better-auth/better-auth.git
synced 2026-10-18 11:32:15 -05:00
fix(oauth-provider): keep approved resources refreshable after a narrowed device poll (#11815)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@better-auth/oauth-provider": patch
|
||||
---
|
||||
|
||||
Device-code clients that request `offline_access` and poll the token endpoint for only some of the RFC 8707 resources the user approved can now refresh for any approved resource. Previously, a refresh for an approved resource left out of the poll failed with `invalid_target`. Device codes polled without `resource`, or for every approved resource, are unaffected.
|
||||
@@ -280,6 +280,7 @@ describe("oauth-provider device-code grant", async () => {
|
||||
/** Registers a public OAuth client able to use the device-code grant. */
|
||||
async function createDeviceClient(
|
||||
grantTypes: string[] = [DEVICE_CODE_GRANT_TYPE],
|
||||
scope = "openid profile email",
|
||||
) {
|
||||
const { headers } = await signInWithTestUser();
|
||||
const created = await auth.api.adminCreateOAuthClient({
|
||||
@@ -287,7 +288,7 @@ describe("oauth-provider device-code grant", async () => {
|
||||
body: {
|
||||
token_endpoint_auth_method: "none",
|
||||
grant_types: grantTypes,
|
||||
scope: "openid profile email",
|
||||
scope,
|
||||
application_type: "native",
|
||||
},
|
||||
});
|
||||
@@ -1256,6 +1257,40 @@ describe("oauth-provider device-code grant", async () => {
|
||||
expect(stored?.deviceCode).toBe(deviceCode);
|
||||
});
|
||||
|
||||
it("keeps every approved resource refreshable after a narrowed exchange", async () => {
|
||||
const scope = "openid offline_access";
|
||||
const clientId = await createDeviceClient(
|
||||
[DEVICE_CODE_GRANT_TYPE, "refresh_token"],
|
||||
scope,
|
||||
);
|
||||
const deviceCode = await approvedDeviceCode(clientId, scope, [
|
||||
resource,
|
||||
secondResource,
|
||||
]);
|
||||
|
||||
const narrowed = await pollToken({
|
||||
grant_type: DEVICE_CODE_GRANT_TYPE,
|
||||
device_code: deviceCode,
|
||||
client_id: clientId,
|
||||
resource,
|
||||
});
|
||||
expect(narrowed.error).toBeNull();
|
||||
expect(decodeJwt(narrowed.data!.access_token as string).aud).not.toContain(
|
||||
secondResource,
|
||||
);
|
||||
|
||||
const refreshed = await pollToken({
|
||||
grant_type: "refresh_token",
|
||||
refresh_token: narrowed.data!.refresh_token as string,
|
||||
client_id: clientId,
|
||||
resource: secondResource,
|
||||
});
|
||||
expect(refreshed.error).toBeNull();
|
||||
expect(decodeJwt(refreshed.data!.access_token as string).aud).toContain(
|
||||
secondResource,
|
||||
);
|
||||
});
|
||||
|
||||
it("single-uses the device code (second exchange fails)", async () => {
|
||||
const clientId = await createDeviceClient();
|
||||
const deviceCode = await approvedDeviceCode(clientId);
|
||||
|
||||
@@ -119,6 +119,7 @@ async function exchangeOAuthDeviceCode(
|
||||
> & { scopes: string[] };
|
||||
const {
|
||||
authorizationContext: { client, confirmation, scopes },
|
||||
claimedDeviceCode,
|
||||
redemptionContext: resources,
|
||||
user,
|
||||
} = await redeemDeviceCode<
|
||||
@@ -187,6 +188,8 @@ async function exchangeOAuthDeviceCode(
|
||||
scopes,
|
||||
user,
|
||||
resources,
|
||||
// Seed refresh tokens with every approved resource, not the narrowed poll.
|
||||
originalResources: claimedDeviceCode.resources ?? undefined,
|
||||
// Forward a sender-constraint a confidential client-auth strategy proved.
|
||||
confirmation,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user