diff --git a/apps/backend/src/app/api/latest/auth/sessions/current/route.tsx b/apps/backend/src/app/api/latest/auth/sessions/current/route.tsx index fbaf24dfd..6df9ab3a5 100644 --- a/apps/backend/src/app/api/latest/auth/sessions/current/route.tsx +++ b/apps/backend/src/app/api/latest/auth/sessions/current/route.tsx @@ -27,7 +27,7 @@ export const DELETE = createSmartRouteHandler({ if (!refreshTokenId) { // Only here for transition period, remove this once all access tokens are updated // TODO next-release - throw new KnownErrors.AccessTokenExpired(new Date()); + throw new KnownErrors.AccessTokenExpired(new Date(), undefined, undefined, undefined); } try { diff --git a/apps/backend/src/lib/tokens.tsx b/apps/backend/src/lib/tokens.tsx index 0e490cf74..2e55ae3e3 100644 --- a/apps/backend/src/lib/tokens.tsx +++ b/apps/backend/src/lib/tokens.tsx @@ -103,7 +103,12 @@ export async function decodeAccessToken(accessToken: string, { allowAnonymous, a }); } catch (error) { if (error instanceof JWTExpired) { - return Result.error(new KnownErrors.AccessTokenExpired(decoded?.exp ? new Date(decoded.exp * 1000) : undefined)); + return Result.error(new KnownErrors.AccessTokenExpired( + decoded?.exp ? new Date(decoded.exp * 1000) : undefined, + decoded?.aud?.toString().split(":")[0], + decoded?.sub ?? undefined, + (decoded?.refresh_token_id ?? decoded?.refreshTokenId) as string | undefined, + )); } else if (error instanceof JOSEError) { console.warn("Unparsable access token. This might be a user error, but if it happens frequently, it's a sign of a misconfiguration.", { accessToken, error }); return Result.error(new KnownErrors.UnparsableAccessToken()); diff --git a/apps/e2e/tests/backend/endpoints/api/v1/users.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/users.test.ts index 8d95ecdf9..91800b883 100644 --- a/apps/e2e/tests/backend/endpoints/api/v1/users.test.ts +++ b/apps/e2e/tests/backend/endpoints/api/v1/users.test.ts @@ -113,7 +113,7 @@ describe("with client access", () => { "code": "ACCESS_TOKEN_EXPIRED", "details": { "expired_at_millis": 1738374988000 }, "error": deindent\` - Access token has expired. Please refresh it and try again. (The access token expired at 2025-02-01T01:56:28.000Z.) + Access token has expired. Please refresh it and try again. (The access token expired at 2025-02-01T01:56:28.000Z.) Project ID: 1234567890. User ID: 1234567890. Refresh token ID: 1234567890. Debug info: Most likely, you fetched the access token before it expired (for example, in a server component, pre-rendered page, or on page load), but then didn't refresh it before it expired. If this is the case, and you're using the SDK, make sure you call getAccessToken() every time you need to use the access token. If you're not using the SDK, make sure you refresh the access token with the refresh endpoint. \`, diff --git a/packages/stack-shared/src/known-errors.tsx b/packages/stack-shared/src/known-errors.tsx index 524d6b756..d4358b995 100644 --- a/packages/stack-shared/src/known-errors.tsx +++ b/packages/stack-shared/src/known-errors.tsx @@ -528,16 +528,26 @@ const UnparsableAccessToken = createKnownErrorConstructor( const AccessTokenExpired = createKnownErrorConstructor( InvalidAccessToken, "ACCESS_TOKEN_EXPIRED", - (expiredAt: Date | undefined) => [ + (expiredAt: Date | undefined, projectId: string | undefined, userId: string | undefined, refreshTokenId: string | undefined) => [ 401, deindent` - Access token has expired. Please refresh it and try again.${expiredAt ? ` (The access token expired at ${expiredAt.toISOString()}.)` : ""} + Access token has expired. Please refresh it and try again.${expiredAt ? ` (The access token expired at ${expiredAt.toISOString()}.)` : ""}${projectId ? ` Project ID: ${projectId}.` : ""}${userId ? ` User ID: ${userId}.` : ""}${refreshTokenId ? ` Refresh token ID: ${refreshTokenId}.` : ""} Debug info: Most likely, you fetched the access token before it expired (for example, in a server component, pre-rendered page, or on page load), but then didn't refresh it before it expired. If this is the case, and you're using the SDK, make sure you call getAccessToken() every time you need to use the access token. If you're not using the SDK, make sure you refresh the access token with the refresh endpoint. `, - { expired_at_millis: expiredAt?.getTime() ?? null }, + { + expired_at_millis: expiredAt?.getTime() ?? null, + project_id: projectId ?? null, + user_id: userId ?? null, + refresh_token_id: refreshTokenId ?? null, + }, + ] as const, + (json: any) => [ + json.expired_at_millis ? new Date(json.expired_at_millis) : undefined, + json.project_id ?? undefined, + json.user_id ?? undefined, + json.refresh_token_id ?? undefined, ] as const, - (json: any) => [json.expired_at_millis ? new Date(json.expired_at_millis) : undefined] as const, ); const InvalidProjectForAccessToken = createKnownErrorConstructor(