Search code examples
oauth-2.0next-auth

How can I separate login and granting access in Next-Auth?


Context: I'm making an app that allows users to sign in with Google and which then makes calls to the Google Ads API on their behalf.

What works: Users can sign in and grant the app the necessary permissions, and the API calls go through successfully.

What I'm struggling with: I'd like to separate the login flow from the permissions flow. In other words, I would like users to be able to log in without granting the app any extraneous permissions, and then, later on, decide if they want to connect to their Google Ads account. This is when Google would prompt them for the necessary permissions.

Relevant code: I'm using next-auth to handle authentication, setting up the Google provider like this:

./src/pages/api/[...nextauth].ts:

[…]
GoogleProvider({
  clientId: env.GOOGLE_CLIENT_ID,
  clientSecret: env.GOOGLE_CLIENT_SECRET,
  authorization: {
    params: {
      scope: 'https://www.googleapis.com/auth/userinfo.email openid https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/adwords'
    }
  }
}),
[…]

You can also look at my reproduction repo, a skeletal app that does nothing but make an API call to Google Ads on behalf of the logged-in user. Bootstrapped with create-t3-app using Next.js, tRPC, Tailwind and Prisma.

UPDATE: I can wrap NextAuth(options) and take control of the endpoints

I've discovered that I can use custom initialization of the NextAuth API routes, which should provide me with an opportunity to alter the scope. Unfortunately, I'm still not sure how to provide an askForGoogleAdsPermissions flag to next-auth/react's signIn function in such a way that my customized API handler will be able to tell the difference and add or omit the authorization key in the Google Provider options accordingly.

Update #2: I can provide extra parameters to signIn

Turns out the signIn function can accept additional parameters as a third argument.

This allows me to separate the login from the permissions, but now the API calls I'm making to Google Ads are failing with "PERMISSION_DENIED: Request had insufficient authentication scopes.", despite having successfully granted permissions.

I can confirm the permissions are granted in my Google account, but when I look at the Prisma database managed by next-auth, I don't see the new scope in my account.

Perhaps I can use the signIn callback to make sure the scope is updated? My problem there is that the account I get as a parameter to my signIn callback has the old scope…


Solution

  • To separate the login and grant flows, I had to:

    1. Include only the login scope in the NextAuth GoogleProvider.
    2. Call the signIn method without arguments for a regular sign in
    3. Call if with extra parameters to override the default scope when connecting to Google Ads: signIn('google', undefined, { scope: googleAdsScope })
    4. Save the new scope to the database in the signIn callback
    5. Make sure this isn't overwritten by a token refresh

    See my repro repo for the full, working code.