Creating Custom OAuth Providers
Add a new OAuth provider to Loopstack by implementing OAuthProviderInterface and registering it with the OAuthProviderRegistry.
The Interface
import { Injectable, OnModuleInit } from '@nestjs/common';import { OAuthProviderInterface, OAuthProviderRegistry, OAuthTokenSet } from '@loopstack/oauth';
@Injectable()export class MyOAuthProvider implements OAuthProviderInterface, OnModuleInit { readonly providerId = 'my-provider'; readonly defaultScopes = ['read', 'write'];
constructor(private registry: OAuthProviderRegistry) {}
onModuleInit() { this.registry.register(this); }
buildAuthUrl(scopes: string[], state: string): string { const params = new URLSearchParams({ client_id: process.env.MY_CLIENT_ID!, redirect_uri: process.env.MY_REDIRECT_URI!, scope: scopes.join(' '), state, response_type: 'code', }); return `https://my-provider.com/oauth/authorize?${params}`; }
async exchangeCode(code: string): Promise<OAuthTokenSet> { // POST to token endpoint, return { accessToken, refreshToken, expiresIn, scope } }
async refreshToken(refreshToken: string): Promise<OAuthTokenSet> { // POST to refresh endpoint, return new token set }}Method Responsibilities
| Method | Purpose |
|---|---|
buildAuthUrl |
Construct the OAuth authorization URL for the user to visit |
exchangeCode |
Exchange the authorization code for tokens after redirect |
refreshToken |
Refresh an expired access token using the refresh token |
OAuthTokenSet
The return type for exchangeCode and refreshToken:
interface OAuthTokenSet { accessToken: string; refreshToken?: string; expiresIn?: number; // seconds until expiry; omit when the provider reports none (never expires) scope: string;}Registration
The provider self-registers via OnModuleInit. Once registered, it’s available to the built-in OAuthWorkflow and OAuthTokenStore.
Create the Module
import { Module } from '@nestjs/common';import { OAuthModule } from '@loopstack/oauth';import { MyOAuthProvider } from './my-oauth-provider';
@Module({ imports: [OAuthModule], providers: [MyOAuthProvider], exports: [MyOAuthProvider],})export class MyOAuthModule {}Usage
Users import your module — no other changes needed:
@Module({ imports: [LoopstackModule.forRoot(), MyOAuthModule],})export class AppModule {}Then use it in workflows:
await this.oAuth.run( { provider: 'my-provider', scopes: ['read', 'write'] }, { callback: { transition: 'authCompleted' } },);Token Lifecycle
OAuthWorkflowcallsbuildAuthUrl()and shows the URL to the user- User completes OAuth in browser, gets redirected back with a code
- Framework calls
exchangeCode()to get tokens - Tokens are stored per user per provider via
OAuthTokenStore OAuthTokenStore.getValidAccessToken()auto-callsrefreshToken()when expired- Tools check for valid tokens and return
{ error: 'unauthorized' }if missing
Existing Providers
| Provider | Module | Provider ID |
|---|---|---|
@loopstack/google-workspace |
'google' |
|
| GitHub | @loopstack/github |
'github' |