nextauthjs / next-auth

Authentication for the Web.
https://authjs.dev
ISC License
24.05k stars 3.33k forks source link

[Auth0] logout then login skips redirection to login page #638

Closed jchoi2x closed 3 years ago

jchoi2x commented 4 years ago

out

Describe the bug When a user first logs in using auth0, they are redirected correctly to auth0's login page and redirected back and authenticated as expected. If the user logs out, then clicks on login again, next-auth doesn't redirect the user to login page again; it automatically logs the user in.

This can be reproduced on the example application

Steps to reproduce

  1. In a new incognito window navigate to https://next-auth-example.now.sh
  2. Login using auth0
  3. When navigated back to https://next-auth-example.now.sh and authenticated, click on the sign out button.
  4. Once signed out, click on Sign In again (The user is not sent to the login page)

Expected behavior The user is redirected to the login page whenever they click on sign in.

iaincollins commented 4 years ago

Hi there, thanks for taking the time to provide detail and a video!

  1. When, NextAuth.js goes to sign in to a provider, typically all providers provide a prompt (Twitter, Facebook, Auth0, etc) the first time, to ask if you want to allow the website you just come from to sign in.
  2. When you sign out you are signing out of your site, but you are not signing out of the Provider (e.g. you are not signing out of Twitter, Facebook, etc).
  3. If you go to sign in to the site again, the provider (Twitter, Facebook, Auth0) usually doesn't prompt, because it was previously authorized, and so it just calls back the site you came from and says it's fine to sign in. This is actually up to the provider and is usually configurable.

So, this is correct and expected behaviour from NextAuth.js - and it's actually down to Auth0 returning a callback immediately.

I appreciate it is however slightly weird flow in the case of Auth0.

There are a couple of options if you don't want this behaviour:

  1. You can call a logout method for the provider.

    NextAuth.js does not currently do this and there are few cases that this would be appropriate and even possible; only Auth0 and perhaps a corporate Open ID service spring to mind, but it's something we could support as an option for providers that support it future (e.g. by implementing the OpenID Connect Logout flow).

    It's an edge case - with most providers (Twitter, Facebook, Google, Apple, etc) it's not a flow you can trigger - so it's not currently a priority but I'd welcome support for it.

  2. You can (and this is probably easier) usually configure the provider to always prompt by specifying a custom authorizationUrl on the Auth0 provider (overriding the default). I don't know how this works for Auth0 - I looked it up but the documentation doesn't say what the options are.

    To do this for Google, you would change this: authorizationUrl: 'https://accounts.google.com/o/oauth2/auth?response_type=code' to this: authorizationUrl: 'https://accounts.google.com/o/oauth2/auth?response_type=code&prompt=consent'

    Several provider have options like this, including Microsoft, which also supports values of 'consent', 'login', 'select_account' and 'none'. https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow

    The default authorizationUrl for Auth0 looks like this: authorizationUrl: https://${options.domain}/authorize?response_type=code'

    The only supported option I can see for Auth0 is 'none'. As it's a commercial service it's probably best to ask them about it and what the options are. This is the page for it, but it doesn't have any info about other options: https://auth0.com/docs/api/authentication#authorization-code-flow

LoriKarikari commented 4 years ago

prompt=consent also works with Auth0. You can find more info about it here.

iaincollins commented 4 years ago

Thanks @LoriKarikari! Do we think Auth0 is a special case where maybe we should add it by default for the provider?

I don't have a strong view on it.

Very happy to add that to the docs for it though - that would seem to make sense!

LoriKarikari commented 4 years ago

@iaincollins reading the page that I linked it seems that the current flow is the default behavior for Auth0.

jchoi2x commented 4 years ago

@iaincollins So is there a solution to this using any of the available features in nextauth or does it still need to implemented?

iaincollins commented 4 years ago

@kizzlebot You can do this in NextAuth.js, as described above.

e.g.

import Providers from `next-auth/providers`
...
providers: [
  Providers.Auth0({
    clientId: process.env.AUTH0_CLIENT_ID,
    clientSecret: process.env.AUTH0_CLIENT_SECRET,
    domain: process.env.AUTH0_DOMAIN,
    authorizationUrl: `https://${process.env.AUTH0_DOMAIN}/authorize?response_type=code&prompt=consent`
  })
}
...
jchoi2x commented 3 years ago

@iaincollins doesn't seem to solve my issue. When I log out and try to log in again, it prompts me for consent like I've never logged out. And there is no way to log out once logged in.

out

This is whats in my pages/api/auth/[...nextAuth].js file

import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'

// For more information on each option (and a full list of options) go to
// https://next-auth.js.org/configuration/options
const options = {
  // https://next-auth.js.org/configuration/providers
  providers: [
    Providers.Auth0({
      clientId: process.env.AUTH0_CLIENT_ID,
      clientSecret: process.env.AUTH0_CLIENT_SECRET,
      domain: process.env.AUTH0_DOMAIN,
      authorizationUrl: `https://${process.env.AUTH0_DOMAIN}/authorize?response_type=code&prompt=consent`
    })
  ],
  // Enable debug messages in the console if you are having problems
  debug: true,
}

export default (req, res) => NextAuth(req, res, options)
cedricmckinnie commented 3 years ago

I don't think prompt=consent is necessarily the solution for this. I believe the issue is that next-auth does not have a logoutUrl property to invalidate a user's session held by the authentication providers. I had trouble finding concise documentation from Auth0 on the matter so here's are good explanations from the FusionAuth folks and AWS Cognito.

FusionAuth Logout: https://fusionauth.io/docs/v1/tech/oauth/endpoints#logout Cognito Logout: https://fusionauth.io/docs/v1/tech/oauth/endpoints#logout

I can think of two possible reasons why it might be a good idea to add a logout flow for providers who support logout flows. Please correct me if I'm wrong!

  1. Account switching: Some users may want to switch account for a particular application. When attempting to sign in right after signing out (session still active), the user is immediately authorized without being prompted with a login screen. As a result, the user cannot enter a different username/email without clearing cookies. Some users may get frustrated by the idea of having to clear their browser cache to switch account.

  2. Security: The OAuth provider is not informed of a logout operation so I don't think the user's session is getting properly terminated. This can present a potential security issue for shared workstations i.e. internet cafes and public libraries. Someone could potentially login as the previous user of a shared workstation by simply clicking the login button on the same site.

Possible solution 1: To include an optional logoutUrl property for relevant providers in the pages/api/auth/[...nextAuth].js config. This would be configured to initiate and handle the logout flow. i.e.

logoutUrl: `https://${process.env.COGNITO_DOMAIN}/logout?client_id=${process.env.COGNITO_CLIENT_ID}`

Possible solution 2: Passing the req and res parameters into event callbacks. This might encourage next-auth users to add complex logic in the event callbacks which may not be ideal.

Possible solution 3: To provide some way to programmatically distinguish a signIn operation from a signOut operation during the "redirect" callback. The two arguments to the redirect callback are url and baseUrl, but perhaps it could include an additional argument such as eventType which enumerates the type of event that invoked the redirect callback. This might encourage next-auth users to add complex logic in the redirect callback which may not be ideal.

My current workaround: I was able to successfully implement the pages/api/logout endpoint myself for both FusionAuth and Cognito.

async function handleLogout(req, res) {

  // Uncomment for FusionAuth
  // res.redirect(`http://${process.env.FUSIONAUTH_DOMAIN}/oauth2/logout?client_id=${process.env.FUSIONAUTH_CLIENT_ID}`);

  // Uncomment for Cognito
  res.redirect(`https://${process.env.COGNITO_DOMAIN}/logout?client_id=${process.env.COGNITO_CLIENT_ID}&logout_uri=${process.env.COGNITO_LOGOUT_URL}`);

}

export default async function logout(req, res) {
  try {
    await handleLogout(req, res);
  } catch (error) {
    console.error(error);
    res.status(error.status || 400).end(error.message);
  }
}

NOTES TO COGNITO USERS

nnals commented 3 years ago

I solved this for Auth0 by setting the authorizationUrl's prompt parameter to login which always shows the authentication page as described here (These are the docs for the new universal login experience but it also works for classic).

Providers.Auth0({
  /* ... */
  authorizationUrl: `https://${process.env.AUTH0_DOMAIN}/authorize?response_type=code&prompt=login`
}),

Maybe this should be added as a tip to https://github.com/nextauthjs/next-auth/blob/main/www/docs/providers/auth0.md...?

After reading https://github.com/nextauthjs/next-auth/blob/main/www/docs/configuration/providers.md#oauth-provider-options which describes the params property of the provider options as "Additional authorization URL parameters" I initially thought I could add prompt: 'login' there but that didn't work. What is that object used for?

Quineone commented 3 years ago

Hi @iaincollins @NickBolles, May I know how you handle the type infer for this ?

Providers.Auth0({
  /* ... */
  authorizationUrl: `https://${process.env.AUTH0_DOMAIN}/authorize?response_type=code&prompt=login`
}),
BrendyRyan commented 3 years ago

Hi, My solution (I am using Microsoft Azure Active Directory and it requires a separate call to logout of the AD):

Create api route api/auth/logout.js :

export default function handler(req, res) {
  res.redirect(https://login.microsoftonline.com/common/oauth2/logout);
}

Then on my logout button I used the optional callback option:

 <button onClick={() => signOut({ callbackUrl: `http://localhost:3000/api/auth/logout` })}>Sign Out</button>

It clears my token, then logs me out of the AAD.

timonweber commented 3 years ago

Hey @iaincollins 👋, we are happily using NextAuth.js, but now we are finding ourselves to have the same issue as described in this thread. Using prompt=consent or promp=login is not an option for us, because we also need to invalidate the access token within the provider when signing out. So this is a big +1 for implementing the OpenID Connect Logout flow and we would be grateful for any hint where to start for a possible workaround. 🙏

marcobusemann commented 3 years ago

Hi, My solution (I am using Microsoft Azure Active Directory and it requires a separate call to logout of the AD):

Create api route api/auth/logout.js :

export default function handler(req, res) {
  res.redirect(https://login.microsoftonline.com/common/oauth2/logout);
}

Then on my logout button I used the optional callback option:

 <button onClick={() => signOut({ callbackUrl: `http://localhost:3000/api/auth/logout` })}>Sign Out</button>

It clears my token, then logs me out of the AAD.

For me this works very well. This is the code i use for Auth0.

export default function handler(req, res) {
    const returnTo = encodeURI("http://localhost:3000/");
    res.redirect(`https://${process.env.AUTH0_DOMAIN}/v2/logout?client_id=${process.env.AUTH0_CLIENT_ID}&returnTo=${returnTo}`);
}

If you use Auth0 with third-party identity providers, you need to additionally add the federated flag as described here: https://auth0.com/docs/api/authentication#logout

fadeojo commented 3 years ago

Log Users Out of Identity Providers

export default function handler(req, res) {
  const returnTo = encodeURI('http://localhost:3000/');
  res.redirect(
    `https://${process.env.AUTH)_DOMAIN}/v2/logout?federated&returnTo=${returnTo}`
  );
}
balazsorban44 commented 3 years ago

Hi there, another maintainer here!

I faced a similar issue with our Identity Server 4 provider at work. Our users use shared computers, and so we would like them to log out of the IdP, when they log out of any of the connected clients. The solution for this is to support federated logout (which is an OpenID Connect feature, and we don't have full coverage of these specs yet) by next-auth.

I raised this issue #836 a while back. Since the canary release, you can now actually create an API endpoint to federate a logout. See https://github.com/nextauthjs/next-auth/issues/836#issuecomment-756019082 for an example.

Here is the spec: https://openid.net/specs/openid-connect-rpinitiated-1_0.html

I reopen #836, and close this issue now, and if anyone would like to implement this built-in to next-auth, feel free to discuss this there, and open a PR! It should need to support all OIDC spec-compliant providers, and probably have some custom handling to those that are not 100% compliant either. It should also support both the JWT and database flows.

spsaucier-bakkt commented 3 years ago

Hi @iaincollins @NickBolles, May I know how you handle the type infer for this ?

Providers.Auth0({
  /* ... */
  authorizationUrl: `https://${process.env.AUTH0_DOMAIN}/authorize?response_type=code&prompt=login`
}),

// @ts-ignore might be all you can hope for here.

rsmcode commented 3 years ago

I solved this for Auth0 by setting the authorizationUrl's prompt parameter to login which always shows the authentication page as described here (These are the docs for the new universal login experience but it also works for classic).

Providers.Auth0({
  /* ... */
  authorizationUrl: `https://${process.env.AUTH0_DOMAIN}/authorize?response_type=code&prompt=login`
}),

Maybe this should be added as a tip to https://github.com/nextauthjs/next-auth/blob/main/www/docs/providers/auth0.md...?

After reading https://github.com/nextauthjs/next-auth/blob/main/www/docs/configuration/providers.md#oauth-provider-options which describes the params property of the provider options as "Additional authorization URL parameters" I initially thought I could add prompt: 'login' there but that didn't work. What is that object used for?

prompt=login doesn't invalidate the user session on Auth0. So should be used with care. For most of the cases, it less than ideal. To invalidate the session and actually log out the user, the application should perform a redirect to v2/logout endpoint.

Log Users Out of Identity Providers

export default function handler(req, res) {
  const returnTo = encodeURI('http://localhost:3000/');
  res.redirect(
    `https://${process.env.AUTH)_DOMAIN}/v2/logout?federated&returnTo=${returnTo}`
  );
}
rajeshbaramol commented 3 years ago

const logout = async () => { const authURL = https://{_domain_}.oktapreview.com/login/signout; await router.push(authURL);
await signOut(); };

This worked for me

schnerd commented 1 year ago

For anyone stumbling upon this thread, the way to force prompt=login with newer versions of next-auth is:

    Auth0Provider({
      // .. other settings
      authorization: {
        params: {
          prompt: "login",
        },
      },
    }),
fuhrthom commented 1 month ago

For anyone using appRouter with NextJS, I got this problem solved like this:

const returnTo = encodeURI(String(window.location.href));
const authURL = `${process.env.NEXT_PUBLIC_AUTH0_ISSUER}/v2/logout?client_id=${process.env.NEXT_PUBLIC_AUTH0_ID}&returnTo=${returnTo}`;
router.push(authURL)
signOut();

I am calling this in my Logout Button and signOut from next-auth/react. ISSUER is my auth0 domain.

Hope this helps someone.