auth0 / express-openid-connect

An Express.js middleware to protect OpenID Connect web applications.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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

StudioFlow opened this issue · comments

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}`,
        });
    }
});

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 #383).

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

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,

@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.

The two options works fine !
Lot of Thanks Adam.

Np @StudioFlow - thanks for testing them out 👍