Skip to main content

Overview

The OAuthProvider interface defines the contract for implementing OAuth 2.0 authentication providers in Arraf Auth. Use this interface to create custom OAuth providers for any service that supports OAuth 2.0.

Interface Definition

The OAuthProvider interface is defined in @arraf-auth/core:
export interface OAuthProvider {
    id: string
    name: string
    clientId: string
    clientSecret: string
    scopes: string[]
    getAuthorizationUrl(state: string, codeVerifier?: string): string
    exchangeCode(code: string, codeVerifier?: string): Promise<OAuthTokens>
    getUserProfile(accessToken: string): Promise<OAuthProfile>
}

Properties

id
string
required
Unique identifier for the provider (e.g., “google”, “github”)
name
string
required
Display name for the provider (e.g., “Google”, “GitHub”)
clientId
string
required
OAuth 2.0 client ID obtained from the provider’s developer console
clientSecret
string
required
OAuth 2.0 client secret obtained from the provider’s developer console
scopes
string[]
required
Array of OAuth scopes to request from the provider

Methods

getAuthorizationUrl

Generates the OAuth authorization URL where users will be redirected to authenticate.
state
string
required
CSRF protection token that will be validated in the callback
codeVerifier
string
PKCE code verifier for enhanced security (optional)
return
string
The complete authorization URL with all required parameters

exchangeCode

Exchanges the authorization code for access tokens.
code
string
required
Authorization code received from the OAuth callback
codeVerifier
string
PKCE code verifier to validate the authorization request
return
Promise<OAuthTokens>
Promise resolving to OAuth tokens

getUserProfile

Fetches the authenticated user’s profile information.
accessToken
string
required
Access token obtained from the token exchange
return
Promise<OAuthProfile>
Promise resolving to the user’s profile data

OAuthTokens

export interface OAuthTokens {
    accessToken: string
    refreshToken?: string
    expiresIn?: number
    tokenType: string
}
accessToken
string
required
The access token for API requests
refreshToken
string
Optional refresh token for obtaining new access tokens
expiresIn
number
Token expiration time in seconds
tokenType
string
required
Token type (typically “Bearer”)

OAuthProfile

export interface OAuthProfile {
    id: string
    email?: string
    phone?: string
    name?: string
    image?: string
    emailVerified?: boolean
}
id
string
required
Provider-specific unique user identifier
email
string
User’s email address
phone
string
User’s phone number
name
string
User’s full name
image
string
URL to user’s profile picture
emailVerified
boolean
Whether the email has been verified by the provider

Implementation Example

Here’s how to implement a custom OAuth provider:
import type { OAuthProvider, OAuthTokens, OAuthProfile } from "@arraf-auth/core"
import { buildAuthorizationUrl, exchangeCodeForTokens } from "@arraf-auth/core"

export interface CustomProviderConfig {
    clientId: string
    clientSecret: string
    redirectUri: string
    scopes?: string[]
}

export function customProvider(config: CustomProviderConfig): OAuthProvider {
    const scopes = config.scopes ?? ["openid", "email", "profile"]

    return {
        id: "custom",
        name: "Custom Provider",
        clientId: config.clientId,
        clientSecret: config.clientSecret,
        scopes,

        getAuthorizationUrl(state: string, codeVerifier?: string) {
            return buildAuthorizationUrl(
                "https://provider.com/oauth/authorize",
                {
                    client_id: config.clientId,
                    redirect_uri: config.redirectUri,
                    response_type: "code",
                    scope: scopes.join(" "),
                    state,
                }
            )
        },

        async exchangeCode(code: string, codeVerifier?: string) {
            const response = await exchangeCodeForTokens(
                "https://provider.com/oauth/token",
                {
                    code,
                    client_id: config.clientId,
                    client_secret: config.clientSecret,
                    redirect_uri: config.redirectUri,
                    grant_type: "authorization_code",
                }
            )

            if (!response.ok) {
                throw new Error("Token exchange failed")
            }

            const tokens = await response.json()

            return {
                accessToken: tokens.access_token,
                refreshToken: tokens.refresh_token,
                expiresIn: tokens.expires_in,
                tokenType: tokens.token_type,
            } satisfies OAuthTokens
        },

        async getUserProfile(accessToken: string) {
            const response = await fetch("https://provider.com/api/user", {
                headers: { Authorization: `Bearer ${accessToken}` },
            })

            if (!response.ok) {
                throw new Error("Failed to fetch user profile")
            }

            const profile = await response.json()

            return {
                id: profile.id,
                email: profile.email,
                name: profile.name,
                image: profile.avatar,
                emailVerified: profile.email_verified,
            } satisfies OAuthProfile
        },
    }
}

Usage

Add your custom provider to the Arraf Auth configuration:
import { ArrafAuth } from "@arraf-auth/core"
import { customProvider } from "./providers/custom"

const auth = new ArrafAuth({
    secret: process.env.AUTH_SECRET,
    database: adapter,
    providers: [
        customProvider({
            clientId: process.env.CUSTOM_CLIENT_ID!,
            clientSecret: process.env.CUSTOM_CLIENT_SECRET!,
            redirectUri: "https://yourdomain.com/api/auth/callback/custom",
        }),
    ],
})
Use the helper functions buildAuthorizationUrl and exchangeCodeForTokens from @arraf-auth/core to simplify OAuth implementation.

Build docs developers (and LLMs) love