Skip to content

Commit 82d60bc

Browse files
chore: return a 204 from the profile endpoint when unauthenticated (#2159)
Co-authored-by: Frederik Prijck <frederik.prijck@auth0.com>
1 parent d916c18 commit 82d60bc

File tree

5 files changed

+86
-1
lines changed

5 files changed

+86
-1
lines changed

src/client/hooks/use-user.integration.test.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,28 @@ describe("useUser Integration with SWR Cache", () => {
146146
// Verify fetch was called twice
147147
expect(fetchSpy).toHaveBeenCalledTimes(2);
148148
});
149+
150+
it("should handle unauthenticated requests to the profile endpoint", async () => {
151+
fetchSpy.mockResolvedValueOnce(
152+
new Response(null, {
153+
status: 204
154+
})
155+
);
156+
157+
const wrapper = ({ children }: { children: React.ReactNode }) => (
158+
<swrModule.SWRConfig value={{ provider: () => new Map() }}>
159+
{children}
160+
</swrModule.SWRConfig>
161+
);
162+
163+
const { result } = renderHook(() => useUser(), { wrapper });
164+
165+
// Wait for the initial data to load
166+
await waitFor(() => expect(result.current.isLoading).toBe(false));
167+
168+
expect(result.current.user).toEqual(null);
169+
expect(result.current.error).toBe(undefined);
170+
expect(fetchSpy).toHaveBeenCalledOnce();
171+
expect(fetchSpy).toHaveBeenCalledWith("/auth/profile");
172+
});
149173
});

src/client/hooks/use-user.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ export function useUser() {
1515
if (!res.ok) {
1616
throw new Error("Unauthorized");
1717
}
18+
19+
if (res.status === 204) {
20+
return null;
21+
}
22+
1823
return res.json();
1924
})
2025
);

src/server/auth-client.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2456,6 +2456,42 @@ ca/T0LLtgmbMmxSv/MmzIg==
24562456
expect(response.status).toEqual(401);
24572457
expect(response.body).toBeNull();
24582458
});
2459+
2460+
it("should return a 204 if the user is not authenticated and noContentProfileResponseWhenUnauthenticated is enabled", async () => {
2461+
const secret = await generateSecret(32);
2462+
const transactionStore = new TransactionStore({
2463+
secret
2464+
});
2465+
const sessionStore = new StatelessSessionStore({
2466+
secret
2467+
});
2468+
const authClient = new AuthClient({
2469+
transactionStore,
2470+
sessionStore,
2471+
2472+
domain: DEFAULT.domain,
2473+
clientId: DEFAULT.clientId,
2474+
clientSecret: DEFAULT.clientSecret,
2475+
2476+
secret,
2477+
appBaseUrl: DEFAULT.appBaseUrl,
2478+
2479+
fetch: getMockAuthorizationServer(),
2480+
2481+
noContentProfileResponseWhenUnauthenticated: true
2482+
});
2483+
2484+
const request = new NextRequest(
2485+
new URL("/auth/profile", DEFAULT.appBaseUrl),
2486+
{
2487+
method: "GET"
2488+
}
2489+
);
2490+
2491+
const response = await authClient.handleProfile(request);
2492+
expect(response.status).toEqual(204);
2493+
expect(response.body).toBeNull();
2494+
});
24592495
});
24602496

24612497
describe("handleCallback", async () => {

src/server/auth-client.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ export interface AuthClientOptions {
138138
httpTimeout?: number;
139139
enableTelemetry?: boolean;
140140
enableAccessTokenEndpoint?: boolean;
141+
noContentProfileResponseWhenUnauthenticated?: boolean;
141142
}
142143

143144
function createRouteUrl(url: string, base: string) {
@@ -175,6 +176,7 @@ export class AuthClient {
175176
private authorizationServerMetadata?: oauth.AuthorizationServer;
176177

177178
private readonly enableAccessTokenEndpoint: boolean;
179+
private readonly noContentProfileResponseWhenUnauthenticated: boolean;
178180

179181
constructor(options: AuthClientOptions) {
180182
// dependencies
@@ -264,6 +266,8 @@ export class AuthClient {
264266
};
265267

266268
this.enableAccessTokenEndpoint = options.enableAccessTokenEndpoint ?? true;
269+
this.noContentProfileResponseWhenUnauthenticated =
270+
options.noContentProfileResponseWhenUnauthenticated ?? false;
267271
}
268272

269273
async handler(req: NextRequest): Promise<NextResponse> {
@@ -595,6 +599,12 @@ export class AuthClient {
595599
const session = await this.sessionStore.get(req.cookies);
596600

597601
if (!session) {
602+
if (this.noContentProfileResponseWhenUnauthenticated) {
603+
return new NextResponse(null, {
604+
status: 204
605+
});
606+
}
607+
598608
return new NextResponse(null, {
599609
status: 401
600610
});

src/server/client.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,14 @@ export interface Auth0ClientOptions {
169169
* See: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps#name-token-mediating-backend
170170
*/
171171
enableAccessTokenEndpoint?: boolean;
172+
173+
/**
174+
* If true, the profile endpoint will return a 204 No Content response when the user is not authenticated
175+
* instead of returning a 401 Unauthorized response.
176+
*
177+
* Defaults to `false`.
178+
*/
179+
noContentProfileResponseWhenUnauthenticated?: boolean;
172180
}
173181

174182
export type PagesRouterRequest = IncomingMessage | NextApiRequest;
@@ -271,7 +279,9 @@ export class Auth0Client {
271279
allowInsecureRequests: options.allowInsecureRequests,
272280
httpTimeout: options.httpTimeout,
273281
enableTelemetry: options.enableTelemetry,
274-
enableAccessTokenEndpoint: options.enableAccessTokenEndpoint
282+
enableAccessTokenEndpoint: options.enableAccessTokenEndpoint,
283+
noContentProfileResponseWhenUnauthenticated:
284+
options.noContentProfileResponseWhenUnauthenticated
275285
});
276286
}
277287

0 commit comments

Comments
 (0)