BaasixBaasix

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

  1. Overview
  2. Supported Providers
  3. Environment Configuration
  4. Provider Setup Guides
  5. API Endpoints
  6. Client Integration
  7. Advanced Configuration
  8. Security Best Practices
  9. Troubleshooting

Overview

Baasix implements OAuth 2.0 with PKCE (Proof Key for Code Exchange) for secure social authentication. The authentication flow:

  1. Client requests an OAuth URL from Baasix
  2. User is redirected to the provider's login page
  3. After successful login, provider redirects back to Baasix callback URL
  4. Baasix exchanges the authorization code for tokens
  5. User profile is fetched and account is created/linked
  6. 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

ProviderStatusFeatures
Google✅ SupportedOpenID Connect, PKCE, ID Token verification
GitHub✅ SupportedOAuth 2.0, Email access
Facebook✅ SupportedOAuth 2.0, Profile picture
Apple✅ SupportedSign 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 In

Google OAuth Configuration

# Google OAuth credentials
GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-google-client-secret

GitHub OAuth Configuration

# GitHub OAuth credentials
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret

Facebook OAuth Configuration

# Facebook OAuth credentials
FACEBOOK_CLIENT_ID=your-facebook-app-id
FACEBOOK_CLIENT_SECRET=your-facebook-app-secret

Apple 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

  1. Go to Google Cloud Console
  2. Create a new project or select an existing one
  3. Navigate to APIs & Services > Credentials
  4. Click Create Credentials > OAuth Client ID
  5. Select Web application as application type
  6. Configure authorized redirect URIs:
    https://api.yourdomain.com/auth/callback/google
  7. Copy the Client ID and Client Secret

Google OAuth Scopes

Default scopes requested:

  • openid - OpenID Connect
  • email - User's email address
  • profile - User's basic profile (name, picture)

GitHub OAuth Setup

  1. Go to GitHub Developer Settings
  2. Click New OAuth App
  3. 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
  4. Click Register application
  5. Generate a new Client Secret

GitHub OAuth Scopes

Default scopes requested:

  • read:user - Read user profile
  • user:email - Access email addresses

Facebook OAuth Setup

  1. Go to Facebook Developers
  2. Create a new app or select an existing one
  3. Add Facebook Login product
  4. Configure Settings > Basic:
    • Note your App ID and App Secret
  5. In Facebook Login > Settings:
    • Add Valid OAuth Redirect URIs:
      https://api.yourdomain.com/auth/callback/facebook

Facebook OAuth Scopes

Default scopes requested:

  • email - User's email address
  • public_profile - Basic profile information

Apple Sign In Setup

Apple Sign In requires more setup than other providers:

  1. Go to Apple Developer Portal
  2. Navigate to Certificates, Identifiers & Profiles
  3. Create an App ID with Sign In with Apple capability
  4. 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
  5. Create a Key with Sign In with Apple enabled:
    • Note the Key ID
    • Download the .p8 private key file
  6. 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"
}
FieldTypeRequiredDescription
providerstringYesOAuth provider: google, github, facebook, apple
callbackURLstringNoCustom callback URL (uses default if not specified)
errorCallbackURLstringNoURL to redirect on error
scopesstring[]NoAdditional OAuth scopes
authModestringNojwt (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 provider
  • state: 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 code
  • state: State parameter
  • id_token: Apple ID token
  • user: 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 FieldGoogleGitHubFacebookApple
emailemailemailemailemail
firstNamegiven_namename (parsed)first_name(from user object)
lastNamefamily_namename (parsed)last_name(from user object)
avatarpictureavatar_urlpicture.data.url-

Multi-tenant Social Auth

When using multi-tenant mode, social sign-in follows these rules:

  1. New Users: Created without tenant association
  2. Existing Users: Matched by email and logged in
  3. 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 seconds

Authentication 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.):

ModeValueDescription
JWTjwt (default)Token returned in response body
CookiecookieToken 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/posts

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
});

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 seconds

When to Use Each Mode

Use CaseRecommended Mode
Same-domain SPAcookie
Cross-domain SPAjwt
Mobile appsjwt
Server-to-serverjwt
Third-party integrationsjwt

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.com

2. 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.com

3. 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).

For production:

AUTH_COOKIE_HTTP_ONLY=true    # Prevent JavaScript access
AUTH_COOKIE_SECURE=true       # HTTPS only
AUTH_COOKIE_SAME_SITE=strict  # Prevent CSRF

6. 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:

  1. Ensure Redis is running and configured
  2. Check if CACHE_ENABLED=true
  3. OAuth state expires after 10 minutes - retry the flow

"redirect_uri_mismatch" Error

Cause: Callback URL doesn't match provider configuration.

Solution:

  1. Verify the exact callback URL in provider settings:
    https://api.yourdomain.com/auth/callback/google
  2. Check for trailing slashes
  3. Ensure protocol matches (https vs http)

Apple Sign In Issues

Issue: "Invalid client_secret"

Solution:

  1. Verify the private key format
  2. Check if the key is associated with the correct Key ID
  3. 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=debug

Testing 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/google

Note: Some providers (like Facebook) require HTTPS even for development. Use tools like ngrok for local testing.


On this page