Custom Providers

Building custom authentication providers for @warpy-auth-sdk/core.

Why Create Custom Providers?

While @warpy-auth-sdk/core comes with built-in providers for popular services, you might need custom providers for:

  • Internal OAuth services
  • Legacy authentication systems
  • Specialized authentication requirements
  • Enterprise identity providers
  • Custom OAuth 2.0 implementations

Provider Types

@warpy-auth-sdk/core supports three types of custom providers:

OAuth Providers

For OAuth 2.0 compatible services:

OAuthProvider

Configuration for OAuth 2.0 providers

interface OAuthProvider { type: 'oauth'; clientId: string; clientSecret: string; authorizeUrl: string; tokenUrl: string; userInfoUrl: string; scope?: string[]; getUser: (token: string) => Promise<User>; }

Email Providers

For custom email-based authentication:

EmailProvider

Configuration for email-based providers

interface EmailProvider { type: 'email'; server: string; from: string; auth?: { user: string; pass: string }; sendMagicLink: (email: string, token: string) => Promise<void>; verifyToken: (token: string) => Promise<boolean>; }

Custom Providers

For completely custom authentication logic:

CustomProvider

Configuration for custom authentication providers

interface CustomProvider { type: 'custom'; authenticate: (request: Request) => Promise<AuthenticateResult>; }

Creating an OAuth Provider

Here's how to create a custom OAuth provider for any OAuth 2.0 service:

Custom OAuth Provider

Example OAuth provider for a custom service

import { OAuthProvider } from '@warpy-auth-sdk/core';

interface CustomOAuthOptions {
  clientId: string;
  clientSecret: string;
  redirectUri: string;
  authorizeUrl: string;
  tokenUrl: string;
  userInfoUrl: string;
  scope?: string[];
}

export function customOAuth(options: CustomOAuthOptions): OAuthProvider {
  return {
    type: 'oauth',
    clientId: options.clientId,
    clientSecret: options.clientSecret,
    authorizeUrl: options.authorizeUrl,
    tokenUrl: options.tokenUrl,
    userInfoUrl: options.userInfoUrl,
    scope: options.scope || ['openid', 'email', 'profile'],
    async getUser(token: string) {
      try {
        const response = await fetch(options.userInfoUrl, {
          headers: {
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json',
          },
        });

        if (!response.ok) {
          throw new Error(`Failed to fetch user info: ${response.statusText}`);
        }

        const userData = await response.json();
        
        // Transform the response to match @warpy-auth-sdk/core user format
        return {
          id: userData.id || userData.sub,
          email: userData.email,
          name: userData.name || userData.display_name,
          picture: userData.picture || userData.avatar_url,
        };
      } catch (error) {
        console.error('Error fetching user info:', error);
        throw new Error('Failed to authenticate user');
      }
    },
  };
}

Using Custom OAuth Provider

Use your custom provider in your authentication configuration:

Using Custom OAuth Provider

Configuration with custom OAuth provider

import { authMiddleware } from '@warpy-auth-sdk/core/next';
import { customOAuth } from './providers/custom-oauth';

const customProvider = customOAuth({
  clientId: process.env.CUSTOM_CLIENT_ID!,
  clientSecret: process.env.CUSTOM_CLIENT_SECRET!,
  redirectUri: process.env.CUSTOM_REDIRECT_URI!,
  authorizeUrl: 'https://api.custom.com/oauth/authorize',
  tokenUrl: 'https://api.custom.com/oauth/token',
  userInfoUrl: 'https://api.custom.com/user',
  scope: ['read', 'write', 'profile'],
});

const handler = authMiddleware(
  {
    secret: process.env.AUTH_SECRET!,
    provider: customProvider,
    callbacks: {
      async user(u) {
        // Custom user processing
        return {
          id: u.id,
          email: u.email,
          name: u.name,
          picture: u.picture,
        };
      },
    },
  },
  {
    basePath: '/api/auth',
    successRedirect: '/dashboard',
    errorRedirect: '/login',
  }
);

Creating an Email Provider

Create a custom email provider for specialized email authentication:

Custom Email Provider

Example email provider with custom logic

import { EmailProvider } from '@warpy-auth-sdk/core';
import nodemailer from 'nodemailer';

interface CustomEmailOptions {
  server: string;
  from: string;
  auth?: { user: string; pass: string };
  template?: {
    subject: string;
    html: string;
    text: string;
  };
}

export function customEmail(options: CustomEmailOptions): EmailProvider {
  const transporter = nodemailer.createTransporter({
    host: options.server.split(':')[0],
    port: parseInt(options.server.split(':')[1]) || 587,
    secure: false,
    auth: options.auth,
  });

  return {
    type: 'email',
    server: options.server,
    from: options.from,
    auth: options.auth,
    async sendMagicLink(email: string, token: string) {
      const magicLink = `${process.env.NEXTAUTH_URL}/api/auth/callback/email?token=${token}`;
      
      const mailOptions = {
        from: options.from,
        to: email,
        subject: options.template?.subject || 'Your magic link',
        html: (options.template?.html || `
          <div>
            <h2>Sign in to your account</h2>
            <p>Click the link below to sign in:</p>
            <a href="${magicLink}">Sign In</a>
            <p>This link will expire in 15 minutes.</p>
          </div>
        `).replace('{{magicLink}}', magicLink),
        text: (options.template?.text || `
          Sign in to your account
          
          Click the link below to sign in:
          ${magicLink}
          
          This link will expire in 15 minutes.
        `).replace('{{magicLink}}', magicLink),
      };

      await transporter.sendMail(mailOptions);
    },
    async verifyToken(token: string) {
      // Implement your token verification logic
      // This could involve checking a database, Redis, etc.
      try {
        // Example: verify JWT token
        const jwt = require('jsonwebtoken');
        const decoded = jwt.verify(token, process.env.AUTH_SECRET!);
        return decoded && decoded.exp > Date.now() / 1000;
      } catch {
        return false;
      }
    },
  };
}

Creating a Custom Provider

For completely custom authentication logic:

Custom Authentication Provider

Example of a completely custom provider

import { CustomProvider } from '@warpy-auth-sdk/core';

interface CustomAuthOptions {
  apiKey: string;
  baseUrl: string;
}

export function customAuth(options: CustomAuthOptions): CustomProvider {
  return {
    type: 'custom',
    async authenticate(request: Request) {
      try {
        // Extract credentials from request
        const body = await request.json();
        const { username, password } = body;

        if (!username || !password) {
          return {
            error: 'Username and password are required',
          };
        }

        // Authenticate with your custom service
        const response = await fetch(`${options.baseUrl}/authenticate`, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${options.apiKey}`,
          },
          body: JSON.stringify({ username, password }),
        });

        if (!response.ok) {
          return {
            error: 'Invalid credentials',
          };
        }

        const userData = await response.json();

        // Return successful authentication result
        return {
          session: {
            user: {
              id: userData.id,
              email: userData.email,
              name: userData.name,
            },
            expires: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours
          },
        };
      } catch (error) {
        return {
          error: 'Authentication failed',
        };
      }
    },
  };
}

Provider Testing

Test your custom providers thoroughly:

Provider Testing

Unit tests for custom providers

import { describe, it, expect, vi } from 'vitest';
import { customOAuth } from './custom-oauth';

describe('Custom OAuth Provider', () => {
  it('should create OAuth provider with correct configuration', () => {
    const provider = customOAuth({
      clientId: 'test-client-id',
      clientSecret: 'test-client-secret',
      redirectUri: 'http://localhost:3000/callback',
      authorizeUrl: 'https://api.test.com/oauth/authorize',
      tokenUrl: 'https://api.test.com/oauth/token',
      userInfoUrl: 'https://api.test.com/user',
    });

    expect(provider.type).toBe('oauth');
    expect(provider.clientId).toBe('test-client-id');
    expect(provider.authorizeUrl).toBe('https://api.test.com/oauth/authorize');
  });

  it('should fetch user info correctly', async () => {
    const provider = customOAuth({
      clientId: 'test-client-id',
      clientSecret: 'test-client-secret',
      redirectUri: 'http://localhost:3000/callback',
      authorizeUrl: 'https://api.test.com/oauth/authorize',
      tokenUrl: 'https://api.test.com/oauth/token',
      userInfoUrl: 'https://api.test.com/user',
    });

    // Mock fetch
    global.fetch = vi.fn().mockResolvedValue({
      ok: true,
      json: () => Promise.resolve({
        id: '123',
        email: 'test@example.com',
        name: 'Test User',
        picture: 'https://example.com/avatar.jpg',
      }),
    });

    const user = await provider.getUser('test-token');
    
    expect(user).toEqual({
      id: '123',
      email: 'test@example.com',
      name: 'Test User',
      picture: 'https://example.com/avatar.jpg',
    });
  });
});

Provider Best Practices

Follow these best practices when creating custom providers:

  • Error Handling: Always handle errors gracefully
  • Type Safety: Use TypeScript for better type safety
  • Testing: Write comprehensive tests for your providers
  • Documentation: Document your provider's configuration options
  • Security: Validate all inputs and handle sensitive data carefully

Provider Configuration

Make your providers configurable and reusable:

Configurable Provider

Example of a configurable custom provider

interface ProviderConfig {
  baseUrl: string;
  apiKey: string;
  timeout?: number;
  retries?: number;
  headers?: Record<string, string>;
}

export function createConfigurableProvider(config: ProviderConfig) {
  return {
    type: 'custom' as const,
    async authenticate(request: Request) {
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), config.timeout || 5000);

      try {
        const response = await fetch(`${config.baseUrl}/auth`, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${config.apiKey}`,
            ...config.headers,
          },
          body: await request.text(),
          signal: controller.signal,
        });

        clearTimeout(timeoutId);

        if (!response.ok) {
          throw new Error(`Authentication failed: ${response.statusText}`);
        }

        const userData = await response.json();
        
        return {
          session: {
            user: {
              id: userData.id,
              email: userData.email,
              name: userData.name,
            },
            expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
          },
        };
      } catch (error) {
        clearTimeout(timeoutId);
        return {
          error: error instanceof Error ? error.message : 'Authentication failed',
        };
      }
    },
  };
}

Provider Examples

Here are some real-world examples of custom providers:

GitHub OAuth Provider

export function github(options: {
  clientId: string;
  clientSecret: string;
  redirectUri: string;
}) {
  return customOAuth({
    ...options,
    authorizeUrl: 'https://github.com/login/oauth/authorize',
    tokenUrl: 'https://github.com/login/oauth/access_token',
    userInfoUrl: 'https://api.github.com/user',
    scope: ['user:email'],
  });
}

Discord OAuth Provider

export function discord(options: {
  clientId: string;
  clientSecret: string;
  redirectUri: string;
}) {
  return customOAuth({
    ...options,
    authorizeUrl: 'https://discord.com/api/oauth2/authorize',
    tokenUrl: 'https://discord.com/api/oauth2/token',
    userInfoUrl: 'https://discord.com/api/users/@me',
    scope: ['identify', 'email'],
  });
}

Custom Providers Complete

You've learned how to create custom providers for @warpy-auth-sdk/core. You can now integrate any authentication system with the SDK.

Next Steps

Now that you can create custom providers, you can:

Custom Providers | @warpy-auth-sdk/core