nextauthjs/next-auth

Add API endpoints at `/api/auth/link` and `/api/auth/unlink`

Closed this issue ยท 6 comments

Add API endpoints at /api/auth/link and /api/auth/unlink for linking and unlinking OAuth accounts. This was a feature of v1 but did not make the cut for v2.0.

Note:

The /api/auth/signin endpoint currently lets you link accounts securely (i.e. if signed in and then you sign in with a different account).

Once a page at /api/auth/link is implemented /api/auth/signin should probably direct users there if the user is signed in. It would effectively be the same as the sign in page, but would only list OAuth providers, and would have 'Unlink' options for providers that are already linked.

Does this let users link social OAuth accounts that don't return an email address?

That's a great question!

So (unless there is an additional check somewhere I have missed) yes it does allow this, which was also the case with version 1. Like version 1, version 2 currently requires an email address to create a user account, but is limited in that it only stores one email address per user.

Once you have a user account, you can link as many accounts as you like (or unlink them all, which is safe because you can still always log in via email).

Not allowing people to unlink all accounts unless an email is specified is good example of an edge case that comes up if you don't require an email address.

There is probably a good discussion to be had around how multiple email addresses could be tracked per user.

e.g. Perhaps it could even use the existing account database (e.g. with provider: 'email', type: 'email' and the email address for the account ID), as it would already enforce uniqueness, but we'd probably want to add a 'verified' flag and also we'd want to consider what we do with the existing user object (would it then be used to store a primary email address?)

Cool, might be a good idea to add to the each OAuth provider's docs which one returns an email address and can be used for both signup and linking and which ones don't return an email address and can only be used for linking.

Curious what the status is of this issue?

Ideally I'd like the following experience:

  • User can sign up with email, Google, Spotify or Soundcloud
  • After signing in, prompt user to connect the rest of their music accounts, e.g. Google (Youtube), Spotify and Soundcloud

Is this still planned?

For anyone that wants to allow users to link an OAuth account to a user, but not register with one, try this advanced initialization (getUser is just a function to retrieve the user from the database):

import type { NextApiRequest, NextApiResponse } from 'next'
import type { NextAuthOptions } from 'next-auth'
import NextAuth from 'next-auth'
import EmailProvider from 'next-auth/providers/email'
import GoogleProvider from 'next-auth/providers/google'
import { getSession } from 'next-auth/react'

export function getNextAuthOptions(req?: NextApiRequest): NextAuthOptions {
  return {
    providers: [
      EmailProvider({ ... }),
      GoogleProvider({ ... }),
    ],

    callbacks: {
      async signIn({ user, account }) {
        if (account.provider === 'email') {
          // Allow login by email if the User already exists
          return Boolean(await getUser({ email: user.email }))
        }

        if (account.provider === 'google') {
          // Allow connecting a Google account to an authenticated User
          if (await getSession({ req })) {
            return true
          }

          // Allow login by an already connected Google account
          if (await getUser({ id: user.id })) {
            return true
          }

          return false
        }

        return false
      },
    },
  }
}

export default async function auth(req: NextApiRequest, res: NextApiResponse) {
  return await NextAuth(req, res, getNextAuthOptions(req))
}

The use case for me here was that users can only register through an email invitation from another user, but should be able to connect their Google account once logged in.

The key was being able to access the user session from within the signIn callback, which took me embarrassingly long to figure out how to do. So hopefully this helps someone save some time!