@adonisjs/ally
Version:
Social authentication provider for AdonisJS
262 lines (261 loc) • 9.7 kB
TypeScript
import type { HttpContext } from '@adonisjs/core/http';
import { Oauth2Client } from '@poppinss/oauth-client/oauth2';
import { type AllyUserContract, type Oauth2AccessToken, type Oauth2DriverConfig, type ApiRequestContract, type AllyDriverContract, type RedirectRequestContract } from '../types.ts';
import { RedirectRequest } from '../redirect_request.ts';
/**
* Abstract base class for implementing OAuth2 social authentication drivers.
* Extends the OAuth2 client to provide AdonisJS-specific functionality like
* CSRF protection, state management, and integration with HTTP context.
*
* @example
* ```ts
* export class CustomDriver extends Oauth2Driver<CustomToken, CustomScopes> {
* protected stateCookieName = 'custom_oauth_state'
* protected stateParamName = 'state'
* protected errorParamName = 'error'
* protected codeParamName = 'code'
* protected authorizeUrl = 'https://provider.com/oauth/authorize'
* protected accessTokenUrl = 'https://provider.com/oauth/token'
* protected scopeParamName = 'scope'
* protected scopesSeparator = ' '
*
* async user() {
* // Implementation
* }
* }
* ```
*/
export declare abstract class Oauth2Driver<Token extends Oauth2AccessToken, Scopes extends string> extends Oauth2Client<Token> implements AllyDriverContract<Token, Scopes> {
#private;
protected ctx: HttpContext;
config: Oauth2DriverConfig;
/**
* Whether the authorization process is stateless. When true,
* state verification via cookies is disabled.
*/
protected isStateless: boolean;
/**
* The cookie name for storing the CSRF state token. Must be unique
* for your driver to avoid conflicts. For example: `gh_oauth_state`
*/
protected abstract stateCookieName: string;
/**
* The cookie name for storing the PKCE code verifier. Define this property
* in child classes that require PKCE.
*/
protected codeVerifierCookieName?: string;
/**
* The query parameter name for sending the state to the OAuth provider.
* This is typically 'state' but varies by provider. Check the provider's
* OAuth documentation.
*/
protected abstract stateParamName: string;
/**
* The query parameter name for error messages returned by the provider
* after authorization redirect. This is typically 'error'.
*/
protected abstract errorParamName: string;
/**
* The query parameter name for the authorization code returned by the
* provider. This is typically 'code'.
*/
protected abstract codeParamName: string;
/**
* The OAuth provider's authorization URL where users are redirected
* to grant permissions.
*/
protected abstract authorizeUrl: string;
/**
* The OAuth provider's endpoint for exchanging the authorization code
* for an access token.
*/
protected abstract accessTokenUrl: string;
/**
* The query parameter name for defining authorization scopes.
* This is typically 'scope'.
*/
protected abstract scopeParamName: string;
/**
* The separator character for joining multiple scopes. This is
* typically a space ' ' or comma ','.
*/
protected abstract scopesSeparator: string;
/**
* Fetch the user details from the OAuth provider using the
* authorization code from the current request.
*
* @param callback - Optional callback to customize the API request
* @returns A promise resolving to the authenticated user profile.
*/
abstract user(callback?: (request: ApiRequestContract) => void): Promise<AllyUserContract<Token>>;
/**
* Fetch user details using an existing access token. Useful for
* verifying tokens or fetching user info for already authenticated users.
*
* @param token - The access token
* @param callback - Optional callback to customize the API request
* @returns A promise resolving to the authenticated user profile.
*/
abstract userFromToken(token: string, callback?: (request: ApiRequestContract) => void): Promise<AllyUserContract<{
token: string;
type: 'bearer';
}>>;
/**
* Check if the current error indicates that the user denied access.
* Different providers use different error codes for access denial.
*
* @returns `true` when the provider reported an access-denied state.
*/
abstract accessDenied(): boolean;
/**
* OAuth protocol version identifier
*/
version: "oauth2";
/**
* Cached state value read from the cookie
*/
protected stateCookieValue?: string;
/**
* Cached PKCE code verifier value read from the cookie via
* loadState
*/
protected codeVerifierCookieValue?: string;
/**
* Create a new OAuth2 driver instance.
*
* @param ctx - The current HTTP context
* @param config - OAuth2 driver configuration
*/
constructor(ctx: HttpContext, config: Oauth2DriverConfig);
/**
* Creates a URL builder instance for constructing authorization URLs
* with scope support.
*
* @param url - The base authorization URL
* @returns A redirect request builder for the given URL.
*/
protected urlBuilder(url: string): RedirectRequest<string>;
/**
* Loads the state value from the encrypted cookie and immediately clears
* the cookie. When PKCE is enabled, it also loads the PKCE code verifier.
* This must be called by child classes in their constructor.
*
* @example
* ```ts
* constructor(ctx: HttpContext, config: DriverConfig) {
* super(ctx, config)
* this.loadState()
* }
* ```
*/
protected loadState(): void;
/**
* Returns the PKCE code verifier for building the authorization redirect.
* This method is expected to create and persist the verifier for later use.
*
* @returns The generated PKCE code verifier or `null` when PKCE is disabled.
*/
protected getPkceCodeVerifierForRedirect(): string | null;
/**
* Returns the PKCE code verifier for the access token exchange.
* This method only reads the verifier that was persisted during redirect.
*
* @returns The persisted PKCE code verifier or `null` when PKCE is disabled.
*/
protected getPkceCodeVerifierForAccessToken(): string | null;
/**
* Enable stateless authentication by disabling CSRF state verification.
* Only use this in scenarios where state verification is not required.
*
* @returns The current driver instance.
*
* @example
* ```ts
* await ally.use('github').stateless().redirect()
* ```
*/
stateless(): this;
/**
* Get the authorization redirect URL without performing the redirect.
* Useful when you need to manually handle the redirect or use the URL
* in a different context.
*
* @param callback - Optional callback to customize the redirect request
* @returns A promise resolving to the authorization URL.
*
* @example
* ```ts
* const url = await ally.use('github').redirectUrl((request) => {
* request.scopes(['user:email'])
* })
* ```
*/
redirectUrl(callback?: (request: RedirectRequestContract<Scopes>) => void): Promise<string>;
/**
* Redirect the user to the OAuth provider's authorization page.
* The state parameter is automatically set for CSRF protection.
*
* @param callback - Optional callback to customize the redirect request
* @returns A promise that resolves after the redirect response is prepared.
*
* @example
* ```ts
* await ally.use('github').redirect((request) => {
* request.scopes(['user:email', 'read:org'])
* request.param('allow_signup', 'false')
* })
* ```
*/
redirect(callback?: (request: RedirectRequestContract<Scopes>) => void): Promise<void>;
/**
* Check if the state parameter from the callback matches the state
* stored in the cookie. Returns false in stateless mode.
*
* @returns `true` when the state validation fails.
*/
stateMisMatch(): boolean;
/**
* Check if an error was returned by the OAuth provider.
*
* @returns `true` when an error exists on the callback request.
*/
hasError(): boolean;
/**
* Get the error code or message returned by the OAuth provider.
* Returns 'unknown_error' if no code is present and no error was specified.
*
* @returns The provider error value when present.
*/
getError(): string | null;
/**
* Get the authorization code from the callback request.
*
* @returns The authorization code when present.
*/
getCode(): string | null;
/**
* Check if the authorization code is present in the callback request.
*
* @returns `true` when the callback request contains an authorization code.
*/
hasCode(): boolean;
/**
* Exchange the authorization code for an access token. This method
* validates the state and checks for errors before making the request.
*
* @param callback - Optional callback to customize the token request
* @returns A promise resolving to the access token payload.
*
* @example
* ```ts
* const token = await ally.use('github').accessToken()
* ```
*/
accessToken(callback?: (request: ApiRequestContract) => void): Promise<Token>;
/**
* Not applicable with OAuth2. Use `userFromToken` instead.
*
* @returns This method never returns.
*/
userFromTokenAndSecret(): Promise<never>;
}