Skip to content

Allow refresh: true in getAccessToken() #2055

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Apr 25, 2025

Conversation

tusharpandey13
Copy link
Contributor

@tusharpandey13 tusharpandey13 commented Apr 11, 2025

Fixes : #1884

Changes

  • Added refresh?: boolean option to getAccessToken()
  • Added integration test for the same that mocks auth0 http calls.

Testing

PASSING

Test Files  12 passed (12)
      Tests  183 passed (183)
   Start at  11:29:35
   Duration  1.61s

Usage

App Router (Server Components, Route Handlers, Server Actions):

When calling getAccessToken without request and response objects, you can pass an options object as the first argument. Set the refresh property to true to force a token refresh.

// app/api/my-api/route.ts
import { getAccessToken } from '@auth0/nextjs-auth0';

export async function GET() {
  try {
    // Force a refresh of the access token
    const { token, expiresAt } = await getAccessToken({ refresh: true });

    // Use the refreshed token
    // ...

    return Response.json({ token, expiresAt });
  } catch (error) {
    console.error('Error getting access token:', error);
    return Response.json({ error: 'Failed to get access token' }, { status: 500 });
  }
}

Pages Router (getServerSideProps, API Routes):

When calling getAccessToken with request and response objects (from getServerSideProps context or an API route), the options object is passed as the third argument.

// pages/api/my-pages-api.ts
import { getAccessToken, withApiAuthRequired } from '@auth0/nextjs-auth0';
import type { NextApiRequest, NextApiResponse } from 'next';

export default withApiAuthRequired(async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  try {
    // Force a refresh of the access token
    const { token, expiresAt } = await getAccessToken(req, res, {
      refresh: true
    });

    // Use the refreshed token
    // ...

    res.status(200).json({ token, expiresAt });
  } catch (error: any) {
    console.error('Error getting access token:', error);
    res.status(error.status || 500).json({ error: error.message });
  }
});

@tusharpandey13 tusharpandey13 requested a review from a team as a code owner April 11, 2025 19:28
@codecov-commenter
Copy link

codecov-commenter commented Apr 11, 2025

Codecov Report

Attention: Patch coverage is 90.24390% with 8 lines in your changes missing coverage. Please review.

Project coverage is 82.53%. Comparing base (64ae3d4) to head (950edb9).

Files with missing lines Patch % Lines
src/server/client.ts 76.47% 8 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2055      +/-   ##
==========================================
+ Coverage   80.02%   82.53%   +2.51%     
==========================================
  Files          21       21              
  Lines        1907     1941      +34     
  Branches      315      342      +27     
==========================================
+ Hits         1526     1602      +76     
+ Misses        376      333      -43     
- Partials        5        6       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@tusharpandey13 tusharpandey13 changed the title feature/forceRefresh getAccessToken Allow refresh: true in getAccessToken() Apr 11, 2025
@cbovis
Copy link

cbovis commented Apr 15, 2025

Thanks for prioritising this @tusharpandey13. I have it as a blocker for upgrading at the moment which blocks our NextJS 15 upgrade. Just wondering if you have a rough idea of when this might get completed and released?

@tusharpandey13
Copy link
Contributor Author

We'll release this soon, ETA might be tomorrow EOD

@cbovis
Copy link

cbovis commented Apr 23, 2025

Any more updates on when we can expect to see this go in? Right now it's a blocker on NextJS 15 upgrade which due to atrocious dev server performance in NextJS 14 is impacting our team's productivity heavily. Frustrating to see what feels ike a very simple change being dragged out.

@tusharpandey13 tusharpandey13 dismissed frederikprijck’s stale review April 25, 2025 06:12

these changes have been added

Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces a new option (refresh) to force a token refresh in getAccessToken(), addressing issue #1884. Key changes include updating method overloads in the Auth0Client, adding a new forceRefresh parameter to the AuthClient’s getTokenSet method, and updating integration tests and documentation to cover this new behavior.

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

File Description
src/server/client.ts Updated getAccessToken overloads to accept refresh option.
src/server/client.test.ts Added integration tests for forcing token refresh.
src/server/auth-client.ts Updated getTokenSet to support forced refresh logic.
EXAMPLES.md Added documentation examples for forcing token refresh.
Comments suppressed due to low confidence (1)

src/server/client.ts:349

  • [nitpick] The parameter names 'arg1', 'arg2', and 'arg3' are ambiguous. Consider renaming them to more descriptive names (e.g., 'reqOrOptions', 'res', 'options') to improve code clarity.
arg1?: PagesRouterRequest | NextRequest | GetAccessTokenOptions,

@tusharpandey13 tusharpandey13 merged commit da33ec8 into main Apr 25, 2025
12 checks passed
@tusharpandey13 tusharpandey13 deleted the feature/forceRefresh-getAccessToken branch April 25, 2025 06:51
@@ -0,0 +1,472 @@
/**
* @fileoverview
Copy link
Member

@frederikprijck frederikprijck Apr 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the seperate file is a good idea, but I would propose to:

  • move the file to client/get-access-token.test.ts
  • Also, as above, drop the integration in the file name.

Comment on lines +6 to +7
* 1. `AuthClient.getTokenSet` correctly uses the refresh token grant to obtain a new
* token set from the authorization server.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldnt matter for this test, as this is an implementation detail.

* @param {string} [options.refreshedRefreshToken="refreshed-refresh-token"] - Refresh token to return on refresh.
* @returns {Promise<vi.Mock>} A Vitest mock function simulating `fetch`.
*/
async function getMockAuthorizationServer({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +306 to +308
vi.spyOn(Auth0Client.prototype as any, "getSession").mockResolvedValue(
initialSession
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's refrain from mocking the getSession, instead, mock the actual session store.

initialSession
);
const mockSaveToSession = vi
.spyOn(Auth0Client.prototype as any, "saveToSession")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's refrain from mocking the saveToSession, but mock the session store.

* it refreshes the token and saves the updated session.
*/
it("should refresh token for pages-router overload when refresh is true", async () => {
const initialExpiresAt = Math.floor(Date.now() / 1000) - 60;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this test working around an expired token, rather than testing that this works when the token is not expired?

* so the test asserts this observed behavior.
*/
it("should refresh token for app-router overload when refresh is true", async () => {
const initialExpiresAt = Math.floor(Date.now() / 1000) - 60;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the token expired in the past, when this test is explicitly to test non-expired token refresh?

Comment on lines +457 to +469
const fetchCalls = mockFetch.mock.calls;
expect(fetchCalls.length).toBeGreaterThan(0);
const tokenEndpointCall = fetchCalls.find((call: any) => {
let urlString: string;
if (call[0] instanceof URL) {
urlString = call[0].toString();
} else if (call[0] instanceof Request) {
urlString = call[0].url;
} else {
urlString = call[0];
}
return urlString.endsWith("/oauth/token");
});
Copy link
Member

@frederikprijck frederikprijck Apr 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not need to assert this, we know this is the case by asserting the returned values. If we want to assert this, I would propose to do it differently, as this doesnt look like what we want.

Comment on lines +433 to +471
expect(result).not.toBeNull();
expect(result?.token).toBe(expectedRefreshedAccessToken);
expect(result?.expiresAt).toBeGreaterThan(initialExpiresAt);
const expectedExpiresAtRough =
Math.floor(Date.now() / 1000) + expectedRefreshedExpiresIn;
expect(result?.expiresAt).toBeGreaterThanOrEqual(
expectedExpiresAtRough - 5
);
expect(result?.expiresAt).toBeLessThanOrEqual(expectedExpiresAtRough + 5);
expect(result?.scope).toBe(initialTokenSet.scope); // Scope remains initial

// 2. Assert saveToSession WAS called (matches current behavior)
expect(mockSaveToSession).toHaveBeenCalledOnce();
const savedSession = mockSaveToSession.mock.calls[0][0] as SessionData;
expect(savedSession.tokenSet.accessToken).toBe(
expectedRefreshedAccessToken
);
expect(savedSession.tokenSet.refreshToken).toBe(
expectedRefreshedRefreshToken
);
expect(savedSession.tokenSet.expiresAt).toBe(result?.expiresAt);
expect(savedSession.tokenSet.scope).toBe(initialTokenSet.scope);

// 3. Assert mockFetch was called
const fetchCalls = mockFetch.mock.calls;
expect(fetchCalls.length).toBeGreaterThan(0);
const tokenEndpointCall = fetchCalls.find((call: any) => {
let urlString: string;
if (call[0] instanceof URL) {
urlString = call[0].toString();
} else if (call[0] instanceof Request) {
urlString = call[0].url;
} else {
urlString = call[0];
}
return urlString.endsWith("/oauth/token");
});
expect(tokenEndpointCall).toBeDefined();
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a test that tests and asserts a lot of things. We can easily split this in several tests to make it more clear what is being tested.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

v4: Bring back the ability to pass refresh: true to getAccessToken
5 participants