Universal OAuth2 client for web and React Native
npm install @idealyst/oauth-clientUniversal OAuth2 client for web and React Native applications with minimal server requirements.
- š Universal: Works on both web and React Native with the same API
- š„ļø Minimal Server: Server only needs to redirect - no token handling required
- š Deep Link Support: Handles mobile OAuth callbacks via custom URL schemes
- š Secure: Uses PKCE flow, client exchanges tokens directly with OAuth provider
- šŖ Storage: Automatic token storage with customizable adapters
- š Refresh: Direct token refresh with OAuth provider
- šÆ TypeScript: Fully typed for better developer experience
``bash`
npm install @idealyst/oauth-clientor
yarn add @idealyst/oauth-client
#### For React Native:
`bash`
npm install @react-native-async-storage/async-storage
#### For Web:
No additional dependencies required.
`typescript
import { createOAuthClient } from '@idealyst/oauth-client'
const client = createOAuthClient({
apiBaseUrl: 'https://api.yourapp.com',
provider: 'google', // Your server endpoint: /auth/google (just redirects)
redirectUrl: 'com.yourapp://oauth/callback',
// OAuth provider config for direct token exchange
issuer: 'https://accounts.google.com',
clientId: 'your-google-client-id',
scopes: ['profile', 'email'],
})
// Works on both web and mobile!
const result = await client.authorize()
console.log('Access token:', result.tokens.accessToken)
`
This library uses a hybrid approach that minimizes server requirements:
2. Server redirects to Google OAuth with server's client credentials + client's PKCE challenge
3. Google redirects back to com.yourapp://oauth/callback?code=123&state=xyz
4. Client automatically detects callback and exchanges code directly with Google using PKCE$3
1. App opens browser to GET /api/auth/google?redirect_uri=com.yourapp://oauth/callback&state=xyz&code_challenge=abc
2. Server redirects to Google OAuth with server's client credentials + client's PKCE challenge
3. Google redirects back to com.yourapp://oauth/callback?code=123&state=xyz
4. Mobile OS opens app via deep link, client exchanges code directly with Google using PKCEMinimal Server Setup
Your server only needs ONE simple endpoint:
$3
`javascript
app.get('/auth/:provider', (req, res) => {
const { redirect_uri, state, scope, code_challenge, code_challenge_method } = req.query
// Build OAuth URL with your server's credentials + client's PKCE
const authUrl = buildOAuthUrl(req.params.provider, {
client_id: process.env.GOOGLE_CLIENT_ID,
redirect_uri: redirect_uri, // Client's redirect URI
state: state, // Client's state for CSRF protection
scope: scope || 'profile email',
response_type: 'code',
code_challenge: code_challenge, // Client's PKCE challenge
code_challenge_method: code_challenge_method || 'S256',
})
res.redirect(authUrl)
})
`That's it! No token exchange, no callbacks, no database - just a simple redirect.
React Native Setup
$3
Add URL scheme to your
Info.plist:`xml
CFBundleURLTypes
CFBundleURLName
com.yourapp.oauth
CFBundleURLSchemes
com.yourapp
`$3
Add intent filter to
android/app/src/main/AndroidManifest.xml:`xml
`$3
The client automatically handles OAuth deep links. The deep link handler is built-in and requires no additional setup.
API Reference
$3
`typescript
const client = createOAuthClient({
// Your server (just redirects)
apiBaseUrl: 'https://api.yourapp.com',
provider: 'google',
redirectUrl: 'com.yourapp://oauth/callback',
// OAuth provider config (for direct token exchange)
issuer: 'https://accounts.google.com',
clientId: 'your-google-client-id',
tokenEndpoint: 'https://oauth2.googleapis.com/token', // Optional
// Optional
scopes: ['profile', 'email'],
additionalParameters: { prompt: 'consent' },
customHeaders: { 'Authorization': 'Bearer api-key' },
})
`$3
#### authorize()
Initiates the OAuth flow and returns tokens.
`typescript
const result = await client.authorize()
// result.tokens: { accessToken, refreshToken, idToken, expiresAt, ... }
`#### refresh(refreshToken)
Refreshes an expired access token directly with OAuth provider.
`typescript
const result = await client.refresh(refreshToken)
`#### getStoredTokens()
Retrieves stored tokens from local storage.
`typescript
const tokens = await client.getStoredTokens()
if (tokens?.expiresAt && tokens.expiresAt < new Date()) {
// Token is expired, refresh it
}
`#### clearStoredTokens()
Clears stored tokens from local storage.
`typescript
await client.clearStoredTokens()
`#### logout()
Clears stored tokens (server handles actual logout/revocation).
`typescript
await client.logout()
`Provider Examples
$3
`typescript
const client = createOAuthClient({
apiBaseUrl: 'https://api.yourapp.com',
provider: 'google',
redirectUrl: 'com.yourapp://oauth/callback',
issuer: 'https://accounts.google.com',
clientId: 'your-google-client-id',
scopes: ['profile', 'email'],
})
`$3
`typescript
const client = createOAuthClient({
apiBaseUrl: 'https://api.yourapp.com',
provider: 'github',
redirectUrl: 'com.yourapp://oauth/callback',
issuer: 'https://github.com',
clientId: 'your-github-client-id',
tokenEndpoint: 'https://github.com/login/oauth/access_token',
scopes: ['user', 'user:email'],
})
`Token Management
`typescript
async function getValidTokens() {
const storedTokens = await client.getStoredTokens()
if (storedTokens) {
// Check if expired
if (storedTokens.expiresAt && storedTokens.expiresAt < new Date()) {
if (storedTokens.refreshToken) {
try {
const refreshed = await client.refresh(storedTokens.refreshToken)
return refreshed.tokens
} catch (error) {
// Refresh failed, need to re-authenticate
await client.clearStoredTokens()
}
}
} else {
return storedTokens
}
} // No valid tokens, start OAuth flow
const result = await client.authorize()
return result.tokens
}
`Error Handling
`typescript
try {
const result = await client.authorize()
} catch (error) {
if (error.message.includes('User cancelled')) {
// User cancelled the authorization
} else if (error.message.includes('Invalid state')) {
// CSRF protection triggered
} else if (error.message.includes('timeout')) {
// User didn't complete OAuth in time (mobile)
} else {
// Other OAuth error
}
}
`Security Benefits
ā
No client secrets in client code - Only client ID needed
ā
PKCE protection - Secure code exchange without client secrets
ā
CSRF protection - Uses state parameter
ā
Direct token exchange - Client communicates directly with OAuth provider
ā
Minimal server attack surface - Server only redirects, doesn't handle tokens
TypeScript
`typescript
import type {
ServerOAuthConfig,
OAuthTokens,
OAuthResult,
OAuthClient
} from '@idealyst/oauth-client'
``MIT