@idealyst/oauth-client
v1.2.140
Published
Universal OAuth2 client for web and React Native
Maintainers
Readme
@idealyst/oauth-client
Universal OAuth2 client for web and React Native applications with minimal server requirements.
Features
- 🌐 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
Installation
npm install @idealyst/oauth-client
# or
yarn add @idealyst/oauth-clientAdditional Dependencies
For React Native:
npm install react-native-inappbrowser-reborn
# Then for iOS:
cd ios && pod installThis provides ASWebAuthenticationSession on iOS (in-app auth sheet, no redirect prompt) and Chrome Custom Tabs on Android (in-app browser overlay).
For Web:
No additional dependencies required.
Quick Start
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)How It Works
This library uses a hybrid approach that minimizes server requirements:
Web Flow:
- Client redirects to
GET /api/auth/google?redirect_uri=com.yourapp://oauth/callback&state=xyz&code_challenge=abc - Server redirects to Google OAuth with server's client credentials + client's PKCE challenge
- Google redirects back to
com.yourapp://oauth/callback?code=123&state=xyz - Client automatically detects callback and exchanges code directly with Google using PKCE
Mobile Flow:
- App opens an in-app auth session (
ASWebAuthenticationSessionon iOS,Chrome Custom Tabson Android) - Auth session navigates to
GET /api/auth/google?redirect_uri=com.yourapp://oauth/callback&state=xyz&code_challenge=abc - Server redirects to Google OAuth with server's client credentials + client's PKCE challenge
- Google redirects back to
com.yourapp://oauth/callback?code=123&state=xyz - Auth session captures the redirect and returns the URL directly to the app (no system prompt on iOS)
Minimal Server Setup
Your server only needs ONE simple endpoint:
GET /auth/{provider} - OAuth Redirect
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
iOS Configuration
Add URL scheme to your Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.yourapp.oauth</string>
<key>CFBundleURLSchemes</key>
<array>
<string>com.yourapp</string>
</array>
</dict>
</array>Android Configuration
Add intent filter to android/app/src/main/AndroidManifest.xml:
<activity android:name=".MainActivity">
<intent-filter android:label="oauth_callback">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="com.yourapp" />
</intent-filter>
</activity>In-App Auth Session (Automatic)
The client uses react-native-inappbrowser-reborn to handle the OAuth flow within an in-app browser session. On iOS this uses ASWebAuthenticationSession which avoids the "Open in App?" system prompt that occurs with Safari redirects. On Android it uses Chrome Custom Tabs. No manual deep link listener setup is required.
API Reference
createOAuthClient(config, storage?)
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' },
})OAuthClient Methods
authorize()
Initiates the OAuth flow and returns tokens.
const result = await client.authorize()
// result.tokens: { accessToken, refreshToken, idToken, expiresAt, ... }refresh(refreshToken)
Refreshes an expired access token directly with OAuth provider.
const result = await client.refresh(refreshToken)getStoredTokens()
Retrieves stored tokens from local storage.
const tokens = await client.getStoredTokens()
if (tokens?.expiresAt && tokens.expiresAt < new Date()) {
// Token is expired, refresh it
}clearStoredTokens()
Clears stored tokens from local storage.
await client.clearStoredTokens()logout()
Clears stored tokens (server handles actual logout/revocation).
await client.logout()Provider Examples
Google OAuth
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'],
})GitHub OAuth
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
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
try {
const result = await client.authorize()
} catch (error) {
if (error.message.includes('User cancelled')) {
// User dismissed the auth session
} else if (error.message.includes('not available')) {
// InAppBrowser not available on device (missing native dependency)
} else if (error.message.includes('Invalid state')) {
// CSRF protection triggered
} 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
import type {
ServerOAuthConfig,
OAuthTokens,
OAuthResult,
OAuthClient
} from '@idealyst/oauth-client'License
MIT
