adminjs icon indicating copy to clipboard operation
adminjs copied to clipboard

[Feature]: Use Redirect URL Returned by `handleLogout` in BaseAuthProvider

Open gugupy opened this issue 3 months ago • 2 comments

Description

Problem

Currently, all AdminJS authentication adapters (@adminjs/express, @adminjs/fastify, @adminjs/nestjs) handle logout by destroying the local session and redirecting to the login URL, for example:

router.get('/logout', (req, res) => {
  req.session.destroy(() => res.redirect(admin.options.rootPath + '/login'));
});

This works for simple local authentication but does not support OAuth/OIDC logout flows, such as with:

  • Keycloak
  • Auth0
  • Azure AD
  • Google Workspace

In these cases, AdminJS only clears the local session but never calls the provider’s logout endpoint — so the user remains authenticated at the provider level. When they revisit the login page, they are automatically re-logged in (silent login).

Why This Matters

Each OAuth provider has its own logout URL (e.g., Keycloak’s protocol/openid-connect/logout), and these need to be called explicitly.

Because AdminJS plugins manage logout internally and redirect immediately, there’s no clean way to integrate provider-specific logout behavior.

Suggested Solution

When the configured BaseAuthProvider implements handleLogout, and that function returns a redirect URL, the adapters should redirect to that URL instead of hardcoding the login redirect. This is help

// Current behavior
router.get('/logout', async (req, res) => {
  await provider.handleLogout(req, res);
  req.session.destroy(() => res.redirect(admin.options.rootPath + '/login'));
});
// Proposed behavior
router.get('/logout', async (req, res) => {
  const redirectUrl = await provider.handleLogout?.(req, res);

  // Always destroy the session
  if (req.session) {
    await new Promise(resolve => req.session.destroy(resolve));
  }

  // If handleLogout returned a URL, redirect there
  if (redirectUrl) {
    return res.redirect(redirectUrl);
  }

  // Otherwise, fallback to AdminJS login path
  return res.redirect(admin.options.rootPath + '/login');
});

Benefits

  • Proper OAuth logout support: enables redirecting to provider logout endpoints.
  • Backward compatible: existing providers continue to work if handleLogout returns undefined.
  • Minimal change: no breaking API changes or new interfaces.

Alternatives

No response

Additional Context

No response

gugupy avatar Oct 18 '25 21:10 gugupy

I'm using Firebase Authentication as provider.

In the handleLogout of my Firebase Provider I revoke the tokens from the current user and then the AdminJs destroys the session and redirect to login afterwards.

export class FirebaseAuthProvider extends BaseAuthProvider<ExpressContext> {
  // ...
  async handleLogout(context: ExpressContext): Promise<void> {
    const token = context.req.session?.adminUser?._auth?.accessToken;

    if (token) {
      await this.revokeFirebaseTokens(token);
    }
  }
}

You can save under the _auth property any identifier of your user in the provider.

leonakao avatar Oct 30 '25 18:10 leonakao

@leonakao I think, this only revoke the token but not the user session in the provider(Keycloak)?

Let me give it a try.

gugupy avatar Oct 31 '25 09:10 gugupy