Skip to content

[Feature]: Multi-Provider Authentication System #1792

@gugupy

Description

@gugupy

Description

Summary

This proposal enhances AdminJS's authentication architecture to natively support multiple, heterogeneous authentication providers (Default, OAuth, SSO, etc.) configured simultaneously. This requires an update to AuthenticationOptions and the introduction of provider-aware session tracking.

Suggested Solution

1. Configuration Changes (AuthenticationOptions)

The existing AuthenticationOptions must be updated to support a map of providers, replacing the single provider property.

Proposed New AuthenticationOptions

The updated type should introduce a new property, providers, to hold a map of all available authentication strategies, and deprecate the single-function authenticate and single provider properties for the multi-auth use case.

export type AuthenticationOptions = {
    cookiePassword: string;
    cookieName?: string;
    
    /**
     * @deprecated Use the 'providers' map instead, with one provider having type 'default'.
     */
    authenticate?: (email: string, password: string, context?: AuthenticationContext) => unknown | null;
    
    /**
     * @deprecated Use the 'providers' map instead.
     */
    provider?: BaseAuthProvider;

    /**
     * New property: A map of unique keys to BaseAuthProvider instances. 
     * Required for multi-provider support (OAuth, SSO, Default combined).
     */
    providers: {
        [key: string]: BaseAuthProvider; // Key is used as providerId for session tracking
    };

    /**
     * @description Maximum number of authorization attempts (if number - per minute)
     */
    maxRetries?: number | AuthenticationMaxRetriesOptions;
};

2. Provider Interface Enhancements (BaseAuthProvider)

The provider interface needs a new method to communicate UI requirements to the AdminJS frontend.

interface LoginProviderUiProps {
    providerId: string;
    
    // Defines how the frontend should interact with this provider
    // 'default' means the authenticate already changed as DefaultAuthProvider
    type: 'default' | 'oauth' | 'sso' ; 
    
    displayName: string; 
    buttonIcon?: string; 
    
    /**
     * EXTENSION: An optional payload for passing provider-specific configuration
     * needed by a custom or overridden frontend component.
     * For example, a Firebase provider might pass 'firebaseConfig' here.
     */
    payload?: Record<string, any>; 
}

// Example usage by a hypothetical FirebaseAuthProvider
export class Keycloak extends BaseAuthProvider {
    // ... constructor takes custom keycloak config

    public getUiProps(): LoginProviderUiProps {
       const { keycloakUrl, realm, clientId, adminJsUrl, loginPath } = this.keycloakConfig;

    const redirectUri = `${adminJsUrl}${loginPath}`;
    const authUrl =
      `${keycloakUrl}/realms/${realm}/protocol/openid-connect/auth?` +
      `client_id=${clientId}&` +
      `redirect_uri=${encodeURIComponent(redirectUri)}&` +
      `response_type=code&` +
      `scope=openid profile email`;

        return {
            providerId: 'keycloak',
            type: 'oauth',
            displayName: 'Sign in with Keycloak,
            buttonIcon: `fa-key`
            // Passing custom details for the frontend component
            payload: {
                authUrl,
            }
        };
    }
}

3. Dynamic UI and Session Management

Frontend Behavior (Login Page)

The AdminJS core aggregates the UI props by iterating over the configured providers map and calling provider.getUiProps().

The default Login Component consumes this list and renders elements dynamically:

  • It renders the full login form for the provider with type: 'default'.
  • It renders clickable buttons (e.g., "Sign in with Google") for providers with type: 'oauth' or type: 'sso', linking directly to the respective loginPath.
  • Use hooks to update currentAdmin provider id.
Backend Session Tracking
  1. Login Success: When any provider's handleLogin method successfully returns a user object, the AdminJS core must inject the provider's key into the CurrentAdmin object before saving it to the session.
interface CurrentAdmin {
    // ... user data
    providerId: string; // New mandatory field to track authentication source
}
  1. Session Actions (Logout/Refresh): For actions like logging out, the AdminJS core will retrieve the providerId from the active CurrentAdmin and delegate the session termination (handleLogout) or refresh (handleRefreshToken) to that specific provider instance. This ensures external sessions (e.g., at an IdP) are also properly managed.

Alternatives

No response

Additional Context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions