-
Notifications
You must be signed in to change notification settings - Fork 730
Description
Description
Summary
Add a callbackPath option to buildAuthenticatedRouter (and equivalent adapters) to support OAuth 2.0 / OpenID Connect login redirects.
Problem
Currently, handleLogin() is only triggered on loginPath (usually via POST), which works for form-based authentication.
However, OAuth/OIDC providers (e.g. Azure AD, Keycloak, Auth0, Okta) redirect back via GET with authorization code and state:
GET /admin/auth/callback?code=xyz&state=abc
Since AdminJS doesn’t expose a built-in GET route for this, developers must manually create custom routes, handle code exchange, manage sessions, and perform redirects — duplicating logic that the plugin could handle natively.
Suggested Solution
Extend all authentication adapters (@adminjs/express, @adminjs/fastify, @adminjs/koa, @adminjs/hapi) to support an optional callbackPath:
const admin = new AdminJS({
componentLoader,
rootPath: '/admin',
callbackPath: '/admin/oauth/callback',
});
buildAuthenticatedRouter(admin, {
provider: myOAuthProvider,
});The adapter should register a GET route that invokes handleLogin() with the query parameters:
router.get(callbackPath, async (req, res) => {
const currentAdmin = await authProvider.handleLogin({ query: req.query }, { req, res });
if (currentAdmin) {
req.session.adminUser = currentAdmin;
return res.redirect(admin.options.rootPath);
}
res.redirect(`${loginPath}?error=oauth_failed`);
});Benefits
- Native OAuth 2.0 / OIDC flow support
- Zero custom routing boilerplate
- Works seamlessly with Keycloak, Azure AD, Auth0, Okta, etc.
- Maintains backward compatibility
- Keeps session handling within AdminJS
Alternatives
Add a custom middleware to handle OAuth callback (GET request with code)
app.get(admin.options.rootPath + '/login', async (req, res, next) => {
// Check if this is a callback with authorization code
if (req.query.code) {
// Instead of handling authentication manually, create a form that POSTs to AdminJS
// This way AdminJS handles the session properly
const html = `
<!DOCTYPE html>
<html>
<head>
<title>Processing Login...</title>
</head>
<body>
<div style="text-align: center; margin-top: 50px;">
<h2>Processing your login...</h2>
<p>Please wait while we complete your authentication.</p>
</div>
<form id="loginForm" method="POST" action="${admin.options.rootPath}/login" style="display: none;">
<input type="hidden" name="code" value="${req.query.code}" />
<input type="hidden" name="redirectUri" value="http://localhost:3000${admin.options.rootPath}/login" />
</form>
<script>
document.getElementById('loginForm').submit();
</script>
</body>
</html>
`;
return res.send(html);
}
// If no code, continue to the normal AdminJS login flow
next();
});Additional Context
No response