Skip to content
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

Logout does not produce the expected behavior when performed by GET <CUSTOM_DOMAIN>/v2/logout call (PKCE flow) #455

Closed
StudioFlow opened this issue Mar 29, 2023 · 6 comments

Comments

@StudioFlow
Copy link

Describe the problem

  • Consider two applications: first is a SPA (@auth0/auth0-react implementation) and second is a Regular Web App (express-openid-connect implementation).
  • This two applications implement the PKCE authentication flow on the same custom domain.
  • When user signs in, the silent login works fine for the two applications (in the 2 ways).
  • When user perform a GET /logout (express-openid-connect route) on the Regular Web APP, he is logged out from alls apps.
  • When user perform a GET <CUSTOM_DOMAIN>/v2/logout call (from the SPA application for example), he is only logged out from the SPA but still logged in the Regular Web Application.

What was the expected behavior?

The GET <CUSTOM_DOMAIN>/v2/logout should performed a logout on all applications (Regular + SPA apps).

URL call sample:

https://<MY_CUSTOM_DOMAIN>/v2/logout?client_id=<CLIENT_ID>&returnTo=<RETURN_URL>&auth0Client=<???>

Environment

  • "node": 16+
  • "express": "4.17.1",
  • "express-openid-connect": "2.12.1"

Auth middleware config:

auth({
    routes: {
        login: false,
        callback: false,
    },
    idpLogout: true,
    authRequired: false,
    auth0Logout: true,
    secret: secret,
    baseURL: config.BASE_URL,
    issuerBaseURL: config.AUTH0_ISSUER,
    clientID: clientId,
    clientSecret: clientSecret,
    authorizationParams: {
        response_type: "code",
        scope: "openid profile email offline_access",
        redirect_uri: `${config.BASE_URL}/callback`,
    }
});

login route override:

app.get("/login", (req, res) => {
    const { audience } = req.query;
    return res.oidc.login({
        idpLogout: true,
        auth0Logout: true,
        returnTo: `/`,
        silent: true,
        authorizationParams: {
            prompt: "none",
            response_type: "code",
            scope: "openid profile email offline_access",
            redirect_uri: `${config.BASE_URL}/callback/&audience=${audience}`,
            audience,
        },
    });
});

callback route override:

app.get("/callback", (req, res) => {
    const { audience, error } = req.query;
    if (error === "login_required" || error === "consent_required") {
        return res.oidc.login({
            idpLogout: true,
            auth0Logout: true,
            returnTo: `/`,
            authorizationParams: {
                response_type: "code",
                scope: "openid profile email offline_access",
                redirect_uri: `${config.BASE_URL}/callback?&audience=${audience}`,
                audience,
            },
        });
    } else {
        return res.oidc.callback({
            redirectUri: `${config.BASE_URL}/callback?&audience=${audience}`,
        });
    }
});
@adamjmcgrath
Copy link
Contributor

When user perform a GET /logout (express-openid-connect route) on the Regular Web APP, he is logged out from alls apps.
When user perform a GET <CUSTOM_DOMAIN>/v2/logout call (from the SPA application for example), he is only logged out from the SPA but still logged in the Regular Web Application.

This difference is due to the different ways SPAs and Regular Web Application's maintain their session.

The SPA SDKs (by default) check the Auth0 Session Layer: (using the web_message response mode) on every page load to maintain a user's logged in status- see https://auth0.com/docs/authenticate/login/configure-silent-authentication#renew-expired-tokens
The Regular Web App SDKs check their local Application Session Layer on every request to maintain a user's session - see https://auth0.com/docs/manage-users/sessions/session-layers

When you visit /v2/logout you will always log the SPA out (since you're logging out of Auth0 and its session always checks auth0), but you will only log the Regular Web Application out by clearing their local application session (by visiting GET /logout (express-openid-connect route)) before visiting /v2/logout (if you have idpLogout: true)

To logout from the Regular web app when you log out of the SPA, you have a few options:

  • easy one: Visit GET /logout (express-openid-connect route) when you want to logout from your SPA. This which will then visit /v2/logout (if you have idpLogout: true) and log the SPA and the Regular Web App out.
  • difficult one: Add some middleware to express that checks the Auth0 session layer (using login with prompt=none) before serving any request and logs the user out if there is no Auth0 session, this will make the Regular Web Application behave more like a SPA
  • future one: Use OIDC Back-Channel Logout - I can't give timelines on when this will be available, but Auth0 plan to support Back-Channel logout and when it does, this SDK will add support. Then when the user logs out via the SPA, this SDK can opt in to get a logout request via the Back-Channel and invalidate the user's session (you can track progress for in backchannel logout token verification #383).

@StudioFlow
Copy link
Author

Got it !
Waiting for the future one, I will test your options.
Thanks !

@StudioFlow
Copy link
Author

Hello Adam,
The first option works fine, thank you, but this involves modifying all our existing SPA to manage the logout.
Moreover, in this solution, the regular web app becomes a single point of failure for all SPA.
That's why, I am now testing the difficult option but I cannot find an exported function which checks the Auth0 session layer.
req.oidc.isAuthenticated() doesn't do the job.
Can you specify it please ?
Regards,

@adamjmcgrath
Copy link
Contributor

@StudioFlow - you would have to visit the authorization server doing a silent login before every requestres.oidc.login({ authorizationParams: { prompt: "none" } }) - this is effectively what a SPA does on every page load, except it uses an iframe - you would have to use redirects.

@StudioFlow
Copy link
Author

The two options works fine !
Lot of Thanks Adam.

@adamjmcgrath
Copy link
Contributor

Np @StudioFlow - thanks for testing them out 👍

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

No branches or pull requests

2 participants