SSO & Social Authentication
This guide covers implementing Single Sign-On (SSO) and social authentication providers in Baasix. Baasix supports multiple OAuth 2.0 providers out of the box including Google, GitHub, Facebook, and Apple.
Table of Contents
- Overview
- Supported Providers
- Environment Configuration
- Provider Setup Guides
- API Endpoints
- Client Integration
- Advanced Configuration
- Security Best Practices
- Troubleshooting
Overview
Baasix implements OAuth 2.0 with PKCE (Proof Key for Code Exchange) for secure social authentication. The authentication flow:
- Client requests an OAuth URL from Baasix
- User is redirected to the provider's login page
- After successful login, provider redirects back to Baasix callback URL
- Baasix exchanges the authorization code for tokens
- User profile is fetched and account is created/linked
- JWT token is returned to the client
Key Features
- Multiple Providers: Google, GitHub, Facebook, and Apple Sign In
- Automatic Account Linking: Link social accounts to existing users by email
- PKCE Support: Enhanced security for OAuth flows
- Cookie or JWT Mode: Flexible token delivery methods
- Multi-tenant Support: Social auth works with multi-tenant configurations
Supported Providers
| Provider | Status | Features |
|---|---|---|
| ✅ Supported | OpenID Connect, PKCE, ID Token verification | |
| GitHub | ✅ Supported | OAuth 2.0, Email access |
| ✅ Supported | OAuth 2.0, Profile picture | |
| Apple | ✅ Supported | Sign in with Apple, Privacy features |
Environment Configuration
Enabling Providers
Configure which authentication services are enabled using the AUTH_SERVICES_ENABLED environment variable:
# Enable specific auth services (comma-separated)
AUTH_SERVICES_ENABLED=LOCAL,GOOGLE,GITHUB,FACEBOOK,APPLE
# LOCAL = email/password authentication
# GOOGLE = Google OAuth
# GITHUB = GitHub OAuth
# FACEBOOK = Facebook OAuth
# APPLE = Apple Sign InGoogle OAuth Configuration
# Google OAuth credentials
GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-google-client-secretGitHub OAuth Configuration
# GitHub OAuth credentials
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secretFacebook OAuth Configuration
# Facebook OAuth credentials
FACEBOOK_CLIENT_ID=your-facebook-app-id
FACEBOOK_CLIENT_SECRET=your-facebook-app-secretApple Sign In Configuration
# Apple Sign In credentials
APPLE_CLIENT_ID=your-apple-service-id
APPLE_CLIENT_SECRET= # Optional, auto-generated from private key
APPLE_TEAM_ID=your-apple-team-id
APPLE_KEY_ID=your-apple-key-id
APPLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"Additional Configuration
# Base URL for OAuth callbacks
BASE_URL=https://api.yourdomain.com
# Allowed application URLs for OAuth redirects
AUTH_APP_URL=https://app.yourdomain.com,https://admin.yourdomain.com
# Cookie configuration (when using cookie mode)
AUTH_COOKIE_HTTP_ONLY=true
AUTH_COOKIE_SECURE=true
AUTH_COOKIE_SAME_SITE=strict
AUTH_COOKIE_DOMAIN=.yourdomain.com
AUTH_COOKIE_PATH=/Provider Setup Guides
Google OAuth Setup
- Go to Google Cloud Console
- Create a new project or select an existing one
- Navigate to APIs & Services > Credentials
- Click Create Credentials > OAuth Client ID
- Select Web application as application type
- Configure authorized redirect URIs:
https://api.yourdomain.com/auth/callback/google - Copy the Client ID and Client Secret
Google OAuth Scopes
Default scopes requested:
openid- OpenID Connectemail- User's email addressprofile- User's basic profile (name, picture)
GitHub OAuth Setup
- Go to GitHub Developer Settings
- Click New OAuth App
- Fill in the application details:
- Application name: Your app name
- Homepage URL:
https://yourdomain.com - Authorization callback URL:
https://api.yourdomain.com/auth/callback/github
- Click Register application
- Generate a new Client Secret
GitHub OAuth Scopes
Default scopes requested:
read:user- Read user profileuser:email- Access email addresses
Facebook OAuth Setup
- Go to Facebook Developers
- Create a new app or select an existing one
- Add Facebook Login product
- Configure Settings > Basic:
- Note your App ID and App Secret
- In Facebook Login > Settings:
- Add Valid OAuth Redirect URIs:
https://api.yourdomain.com/auth/callback/facebook
- Add Valid OAuth Redirect URIs:
Facebook OAuth Scopes
Default scopes requested:
email- User's email addresspublic_profile- Basic profile information
Apple Sign In Setup
Apple Sign In requires more setup than other providers:
- Go to Apple Developer Portal
- Navigate to Certificates, Identifiers & Profiles
- Create an App ID with Sign In with Apple capability
- Create a Services ID for web authentication:
- Note the Identifier (this is your
APPLE_CLIENT_ID) - Configure domains and redirect URLs:
Domain: api.yourdomain.com Return URL: https://api.yourdomain.com/auth/callback/apple
- Note the Identifier (this is your
- Create a Key with Sign In with Apple enabled:
- Note the Key ID
- Download the
.p8private key file
- Note your Team ID from the top right of the developer portal
Converting Apple Private Key
The private key should be in PEM format. If using environment variables, replace newlines with \n:
APPLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIGT...your-key-here...\n-----END PRIVATE KEY-----"API Endpoints
Initiate Social Sign In
Start the OAuth flow by requesting an authorization URL.
Endpoint: POST /auth/social/signin
Request Body:
{
"provider": "google",
"callbackURL": "https://api.yourdomain.com/auth/callback/google",
"errorCallbackURL": "https://app.yourdomain.com/auth/error",
"scopes": ["email", "profile"],
"authMode": "jwt"
}| Field | Type | Required | Description |
|---|---|---|---|
| provider | string | Yes | OAuth provider: google, github, facebook, apple |
| callbackURL | string | No | Custom callback URL (uses default if not specified) |
| errorCallbackURL | string | No | URL to redirect on error |
| scopes | string[] | No | Additional OAuth scopes |
| authMode | string | No | jwt (default) or cookie |
Response:
{
"redirect": true,
"url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=...&redirect_uri=...&state=..."
}OAuth Callback
After user authorizes, the provider redirects to this callback endpoint.
Endpoint: GET /auth/callback/:provider
Query Parameters:
code: Authorization code from providerstate: State parameter for CSRF protection
Response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"authMode": "jwt",
"user": {
"id": "user-uuid",
"email": "user@example.com",
"firstName": "John",
"lastName": "Doe"
},
"role": {
"id": "role-uuid",
"name": "user"
},
"permissions": [...],
"tenant": null
}Apple Sign In Callback (POST)
Apple Sign In uses a POST callback with form data.
Endpoint: POST /auth/callback/apple
Form Data:
code: Authorization codestate: State parameterid_token: Apple ID tokenuser: User info (first sign-in only)
Client Integration
React/Next.js Example
import { useState } from 'react';
const API_URL = 'https://api.yourdomain.com';
export function SocialLoginButtons() {
const [loading, setLoading] = useState<string | null>(null);
const handleSocialLogin = async (provider: string) => {
setLoading(provider);
try {
const response = await fetch(`${API_URL}/auth/social/signin`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
provider,
authMode: 'jwt',
}),
});
const data = await response.json();
if (data.redirect && data.url) {
// Redirect to OAuth provider
window.location.href = data.url;
}
} catch (error) {
console.error('Social login error:', error);
} finally {
setLoading(null);
}
};
return (
<div className="flex flex-col gap-3">
<button
onClick={() => handleSocialLogin('google')}
disabled={loading !== null}
className="flex items-center justify-center gap-2 px-4 py-2 border rounded-lg hover:bg-gray-50"
>
<GoogleIcon />
{loading === 'google' ? 'Redirecting...' : 'Continue with Google'}
</button>
<button
onClick={() => handleSocialLogin('github')}
disabled={loading !== null}
className="flex items-center justify-center gap-2 px-4 py-2 border rounded-lg hover:bg-gray-50"
>
<GitHubIcon />
{loading === 'github' ? 'Redirecting...' : 'Continue with GitHub'}
</button>
<button
onClick={() => handleSocialLogin('facebook')}
disabled={loading !== null}
className="flex items-center justify-center gap-2 px-4 py-2 border rounded-lg hover:bg-gray-50"
>
<FacebookIcon />
{loading === 'facebook' ? 'Redirecting...' : 'Continue with Facebook'}
</button>
<button
onClick={() => handleSocialLogin('apple')}
disabled={loading !== null}
className="flex items-center justify-center gap-2 px-4 py-2 bg-black text-white rounded-lg hover:bg-gray-800"
>
<AppleIcon />
{loading === 'apple' ? 'Redirecting...' : 'Continue with Apple'}
</button>
</div>
);
}Handling OAuth Callback in Client
Create a callback page to handle the OAuth response:
// pages/auth/callback/[provider].tsx
import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
export default function OAuthCallback() {
const router = useRouter();
const [error, setError] = useState<string | null>(null);
useEffect(() => {
// The API returns JSON, so we need to handle the response
// If using cookie mode, the token is set automatically
// If using JWT mode, store the token from the response
const handleCallback = async () => {
const params = new URLSearchParams(window.location.search);
const token = params.get('token');
const errorMsg = params.get('error');
if (errorMsg) {
setError(errorMsg);
return;
}
if (token) {
// Store token (e.g., in localStorage or state management)
localStorage.setItem('authToken', token);
router.push('/dashboard');
}
};
handleCallback();
}, [router]);
if (error) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="text-center">
<h1 className="text-xl font-bold text-red-600">Authentication Error</h1>
<p className="mt-2">{error}</p>
<button
onClick={() => router.push('/login')}
className="mt-4 px-4 py-2 bg-blue-500 text-white rounded"
>
Back to Login
</button>
</div>
</div>
);
}
return (
<div className="flex items-center justify-center min-h-screen">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto"></div>
<p className="mt-4">Completing authentication...</p>
</div>
</div>
);
}Mobile App Integration (React Native)
For mobile apps, use a WebView or in-app browser for OAuth:
import { useState } from 'react';
import { WebView } from 'react-native-webview';
import { Modal } from 'react-native';
const API_URL = 'https://api.yourdomain.com';
export function SocialLogin() {
const [oauthUrl, setOauthUrl] = useState<string | null>(null);
const handleSocialLogin = async (provider: string) => {
const response = await fetch(`${API_URL}/auth/social/signin`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ provider }),
});
const data = await response.json();
if (data.url) {
setOauthUrl(data.url);
}
};
const handleNavigationStateChange = (navState: any) => {
// Check if the URL is the callback URL
if (navState.url.includes('/auth/callback/')) {
// Parse the response and extract token
// Close the WebView and store the token
setOauthUrl(null);
}
};
return (
<>
<Button title="Sign in with Google" onPress={() => handleSocialLogin('google')} />
<Modal visible={!!oauthUrl} animationType="slide">
{oauthUrl && (
<WebView
source={{ uri: oauthUrl }}
onNavigationStateChange={handleNavigationStateChange}
/>
)}
</Modal>
</>
);
}Advanced Configuration
Custom User Profile Mapping
Each provider returns different profile data. Baasix automatically maps common fields:
| Baasix Field | GitHub | Apple | ||
|---|---|---|---|---|
| firstName | given_name | name (parsed) | first_name | (from user object) |
| lastName | family_name | name (parsed) | last_name | (from user object) |
| avatar | picture | avatar_url | picture.data.url | - |
Multi-tenant Social Auth
When using multi-tenant mode, social sign-in follows these rules:
- New Users: Created without tenant association
- Existing Users: Matched by email and logged in
- Tenant Selection: After login, user can be invited to or select a tenant
Session Management with Social Auth
Social auth sessions are managed the same way as regular sessions:
# Session configuration
ACCESS_TOKEN_EXPIRES_IN=604800 # 7 days in secondsAuthentication Modes (authMode)
Baasix supports two authentication modes for token delivery. You can specify the authMode parameter in any authentication request (login, register, social sign-in, etc.):
| Mode | Value | Description |
|---|---|---|
| JWT | jwt (default) | Token returned in response body |
| Cookie | cookie | Token set as HTTP-only cookie |
JWT Mode (Default)
In JWT mode, the token is returned in the response body. Your client application must store and manage the token:
// Request
{
"email": "user@example.com",
"password": "password123",
"authMode": "jwt"
}
// Response
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"authMode": "jwt",
"user": { ... }
}Usage in subsequent requests:
curl -H "Authorization: Bearer YOUR_TOKEN_HERE" \
https://api.yourdomain.com/items/postsCookie Mode
In cookie mode, the token is automatically set as an HTTP-only cookie. This is more secure for same-domain SPAs as JavaScript cannot access the token:
// Request
{
"email": "user@example.com",
"password": "password123",
"authMode": "cookie"
}
// Response
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"message": "Authentication successful",
"authMode": "cookie",
"user": { ... }
}
// Cookie is also set: token=eyJ...Cookie is automatically sent with requests (no manual header needed):
// Fetch with credentials
fetch('https://api.yourdomain.com/items/posts', {
credentials: 'include' // Important: include cookies
});
// Axios with credentials
axios.get('https://api.yourdomain.com/items/posts', {
withCredentials: true // Important: include cookies
});Cookie Configuration
Configure cookie behavior with environment variables:
# Cookie security settings
AUTH_COOKIE_HTTP_ONLY=true # Prevent JavaScript access (default: true)
AUTH_COOKIE_SECURE=true # HTTPS only (default: true in production)
AUTH_COOKIE_SAME_SITE=strict # CSRF protection: strict, lax, or none (default: strict in prod, lax in dev)
AUTH_COOKIE_DOMAIN=.yourdomain.com # Share across subdomains (optional)
AUTH_COOKIE_PATH=/ # Cookie path (default: /)
# Token expiration (applies to both modes)
ACCESS_TOKEN_EXPIRES_IN=604800 # 7 days in secondsWhen to Use Each Mode
| Use Case | Recommended Mode |
|---|---|
| Same-domain SPA | cookie |
| Cross-domain SPA | jwt |
| Mobile apps | jwt |
| Server-to-server | jwt |
| Third-party integrations | jwt |
Social Auth with authMode
For social authentication, pass authMode when initiating the OAuth flow:
// Request to /auth/social/signin
{
"provider": "google",
"authMode": "cookie" // Token will be set as cookie after OAuth callback
}The authMode is stored with the OAuth state and applied when the user completes the OAuth flow.
Security Best Practices
1. Use HTTPS Only
Always use HTTPS in production for OAuth callbacks:
BASE_URL=https://api.yourdomain.com
AUTH_APP_URL=https://app.yourdomain.com2. Validate Redirect URIs
Baasix validates callback URLs against AUTH_APP_URL:
# Comma-separated list of allowed app URLs
AUTH_APP_URL=https://app.yourdomain.com,https://admin.yourdomain.com3. State Parameter Protection
Baasix automatically generates and validates state parameters to prevent CSRF attacks.
4. PKCE Support
PKCE (Proof Key for Code Exchange) is enabled by default for providers that support it (Google).
5. Secure Cookie Configuration
For production:
AUTH_COOKIE_HTTP_ONLY=true # Prevent JavaScript access
AUTH_COOKIE_SECURE=true # HTTPS only
AUTH_COOKIE_SAME_SITE=strict # Prevent CSRF6. Keep Secrets Secure
- Never commit OAuth secrets to version control
- Use environment variables or secrets management
- Rotate secrets periodically
Troubleshooting
Common Issues
"Provider not found" Error
Cause: Provider not enabled or credentials not configured.
Solution: Check your environment variables:
# Ensure provider is in AUTH_SERVICES_ENABLED
AUTH_SERVICES_ENABLED=LOCAL,GOOGLE
# Ensure credentials are set
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=..."Invalid or expired state" Error
Cause: OAuth state parameter expired or cache issue.
Solution:
- Ensure Redis is running and configured
- Check if
CACHE_ENABLED=true - OAuth state expires after 10 minutes - retry the flow
"redirect_uri_mismatch" Error
Cause: Callback URL doesn't match provider configuration.
Solution:
- Verify the exact callback URL in provider settings:
https://api.yourdomain.com/auth/callback/google - Check for trailing slashes
- Ensure protocol matches (https vs http)
Apple Sign In Issues
Issue: "Invalid client_secret"
Solution:
- Verify the private key format
- Check if the key is associated with the correct Key ID
- Ensure the Service ID (client ID) is correct
Issue: "Invalid redirect_uri"
Solution: Apple requires domains to be verified in the developer portal.
Debug Mode
Enable debugging for more detailed logs:
LOG_LEVEL=debugTesting Locally
For local development, use localhost callbacks:
BASE_URL=http://localhost:3000
AUTH_APP_URL=http://localhost:3001
# In provider settings, add:
# http://localhost:3000/auth/callback/googleNote: Some providers (like Facebook) require HTTPS even for development. Use tools like ngrok for local testing.
Related Documentation
- Authentication API - Email/password authentication
- Multi-tenant Guide - Tenant-based authentication
- Session Limits Feature - Control concurrent sessions
- Deployment Guide - Environment configuration
- Integration Guide - Client-side implementation