Files
gatehouse-api/docs/external-auth-api.md
JamesBhattarai 1ba5738d52 Feat: OIDC UI bridge, Microsoft SSO,, and schema session flaws
- OAuth Callback to Use Gatehouse UI to login instead of Backend Served dull ui
- Setup Autoregister of user + org, on oauth
- Microsoft Oauth Support
- OIDCRefreshToken.access_token_id  had a narrow Column increased to VAR(255) and remove FK to sessions.id which had no use
- client_id and client.id mismatch ,backup-code consumption
2026-02-26 23:18:31 +05:45

18 KiB

External Authentication API Documentation

Overview

The Gatehouse External Authentication API provides endpoints for integrating external OAuth providers (Google, GitHub, Microsoft) for user authentication and account linking. This API supports three main workflows:

  1. Provider Configuration - Organization admins can configure OAuth credentials
  2. Account Linking - Users can link external accounts to their Gatehouse account
  3. OAuth Authentication - Users can authenticate using their external provider credentials

Base URL

https://api.gatehouse.dev/v1

Authentication

All endpoints require Bearer token authentication except for OAuth callback endpoints.

Authorization: Bearer <your-jwt-token>

Error Codes

Code Type Description
400 BAD_REQUEST Invalid request parameters
401 UNAUTHORIZED Authentication required
403 FORBIDDEN Insufficient permissions
404 NOT_FOUND Resource not found
500 INTERNAL_ERROR Server error

External Auth Specific Error Types

Error Type HTTP Code Description
PROVIDER_NOT_CONFIGURED 400 Provider not configured for organization
INVALID_REDIRECT_URI 400 Redirect URI not allowed
INVALID_STATE 400 Invalid or expired OAuth state
INVALID_FLOW_TYPE 400 Invalid flow type
PROVIDER_MISMATCH 400 Provider mismatch in flow
PROVIDER_NOT_LINKED 400 Provider not linked to account
CANNOT_UNLINK_LAST 400 Cannot unlink last authentication method
ACCOUNT_NOT_FOUND 400 No matching Gatehouse account
EMAIL_EXISTS 400 Email already exists
UNSUPPORTED_PROVIDER 400 Provider not supported

Endpoints

Provider Configuration

List Available Providers

GET /api/v1/auth/external/providers

List all available external authentication providers for the current organization.

Authentication: Required (any authenticated user)

Response (200):

{
  "version": "1.0",
  "success": true,
  "code": 200,
  "data": {
    "providers": [
      {
        "id": "google",
        "name": "Google",
        "type": "google",
        "is_configured": true,
        "is_active": true,
        "settings": {
          "requires_domain": false,
          "supports_refresh_tokens": true
        }
      },
      {
        "id": "github",
        "name": "GitHub",
        "type": "github",
        "is_configured": false,
        "is_active": false,
        "settings": {
          "requires_domain": false,
          "supports_refresh_tokens": true
        }
      }
    ]
  }
}

Get Provider Configuration

GET /api/v1/auth/external/providers/{provider}/config

Get provider configuration (admin only).

Authentication: Required (Organization Admin or Owner)

Path Parameters:

Parameter Type Description
provider string Provider type (google, github, microsoft)

Response (200):

{
  "version": "1.0",
  "success": true,
  "code": 200,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "organization_id": "550e8400-e29b-41d4-a716-446655440001",
    "provider_type": "google",
    "client_id": "client-id.apps.googleusercontent.com",
    "auth_url": "https://accounts.google.com/o/oauth2/v2/auth",
    "token_url": "https://oauth2.googleapis.com/token",
    "userinfo_url": "https://www.googleapis.com/oauth2/v3/userinfo",
    "scopes": ["openid", "profile", "email"],
    "redirect_uris": [
      "https://app.gatehouse.dev/auth/external/google/callback"
    ],
    "is_active": true,
    "settings": {
      "hosted_domain": "example.com"
    },
    "created_at": "2024-01-15T10:30:00Z",
    "updated_at": "2024-01-20T14:45:00Z"
  }
}

Error Response (403):

{
  "version": "1.0",
  "success": false,
  "code": 403,
  "message": "Admin access required",
  "error_type": "FORBIDDEN"
}

Error Response (404):

{
  "version": "1.0",
  "success": false,
  "code": 404,
  "message": "Google OAuth is not configured",
  "error_type": "NOT_FOUND"
}

Create/Update Provider Configuration

POST /api/v1/auth/external/providers/{provider}/config

Create or update provider configuration (admin only).

Authentication: Required (Organization Admin or Owner)

Path Parameters:

Parameter Type Description
provider string Provider type (google, github, microsoft)

Request Body:

{
  "client_id": "client-id.apps.googleusercontent.com",
  "client_secret": "client-secret",
  "scopes": ["openid", "profile", "email"],
  "redirect_uris": [
    "https://app.gatehouse.dev/auth/external/google/callback",
    "http://localhost:3000/callback"
  ],
  "settings": {
    "hosted_domain": "example.com",
    "access_type": "offline",
    "prompt": "consent"
  },
  "is_active": true
}

Response (201 - Created):

{
  "version": "1.0",
  "success": true,
  "code": 201,
  "message": "Provider configuration created successfully",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "provider_type": "google",
    "client_id": "client-id.apps.googleusercontent.com",
    "is_active": true
  }
}

Response (200 - Updated):

{
  "version": "1.0",
  "success": true,
  "code": 200,
  "message": "Provider configuration updated successfully",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "provider_type": "google",
    "client_id": "updated-client-id",
    "is_active": true
  }
}

Delete Provider Configuration

DELETE /api/v1/auth/external/providers/{provider}/config

Delete provider configuration (admin only).

Authentication: Required (Organization Admin or Owner)

Path Parameters:

Parameter Type Description
provider string Provider type (google, github, microsoft)

Response (200):

{
  "version": "1.0",
  "success": true,
  "code": 200,
  "message": "Google provider configuration deleted successfully"
}

Account Linking

List Linked Accounts

GET /api/v1/auth/external/linked-accounts

List all linked external accounts for the current user.

Authentication: Required (any authenticated user)

Response (200):

{
  "version": "1.0",
  "success": true,
  "code": 200,
  "data": {
    "linked_accounts": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440002",
        "provider_type": "google",
        "provider_user_id": "123456789",
        "email": "user@gmail.com",
        "name": "John Doe",
        "picture": "https://lh3.googleusercontent.com/...",
        "verified": true,
        "linked_at": "2024-01-15T10:30:00Z",
        "last_used_at": "2024-01-20T14:45:00Z"
      },
      {
        "id": "550e8400-e29b-41d4-a716-446655440003",
        "provider_type": "github",
        "provider_user_id": "987654321",
        "email": "user@github.com",
        "name": "johndoe",
        "picture": "https://avatars.githubusercontent.com/...",
        "verified": true,
        "linked_at": "2024-01-10T08:00:00Z",
        "last_used_at": null
      }
    ],
    "unlink_available": true
  }
}

Initiate Account Linking

POST /api/v1/auth/external/{provider}/link

Initiate OAuth flow to link an external account.

Authentication: Required (any authenticated user)

Path Parameters:

Parameter Type Description
provider string Provider type (google, github, microsoft)

Request Body:

{
  "redirect_uri": "https://app.gatehouse.dev/settings/security"
}

Response (200):

{
  "version": "1.0",
  "success": true,
  "code": 200,
  "data": {
    "authorization_url": "https://accounts.google.com/o/oauth2/v2/auth?...",
    "state": "eyJmbG93X3R5cGUiOiJsaW5rIiwicHJvdmlkZXIiOiJnb29nbGUifQ..."
  },
  "message": "Link flow initiated. Redirect to authorization URL."
}

Error Response (400 - Provider not configured):

{
  "version": "1.0",
  "success": false,
  "code": 400,
  "message": "Google OAuth is not configured for this organization",
  "error_type": "PROVIDER_NOT_CONFIGURED"
}

DELETE /api/v1/auth/external/{provider}/unlink

Unlink an external account from the user's profile.

Authentication: Required (any authenticated user)

Path Parameters:

Parameter Type Description
provider string Provider type (google, github, microsoft)

Response (200):

{
  "version": "1.0",
  "success": true,
  "code": 200,
  "message": "Google account unlinked successfully"
}

Error Response (400 - Last auth method):

{
  "version": "1.0",
  "success": false,
  "code": 400,
  "message": "Cannot unlink the last authentication method",
  "error_type": "CANNOT_UNLINK_LAST"
}

Error Response (404 - Not linked):

{
  "version": "1.0",
  "success": false,
  "code": 400,
  "message": "Provider not linked",
  "error_type": "PROVIDER_NOT_LINKED"
}

OAuth Flow

Initiate OAuth Authorization

GET /api/v1/auth/external/{provider}/authorize

Initiate OAuth authentication or account registration flow.

Authentication: Not required (for login/register flows)

Path Parameters:

Parameter Type Description
provider string Provider type (google, github, microsoft)

Query Parameters:

Parameter Required Description
flow Yes Flow type: login or register
redirect_uri No Override redirect URI (must be in allowed list)
organization_id No Organization context for SSO

Response (200):

{
  "version": "1.0",
  "success": true,
  "code": 200,
  "data": {
    "authorization_url": "https://accounts.google.com/o/oauth2/v2/auth?...",
    "state": "eyJmbG93X3R5cGUiOiJsb2dpbiIsInByb3ZpZGVyIjoiZ29vZ2xlIn0..."
  },
  "message": "OAuth login flow initiated"
}

Error Response (400 - Invalid flow):

{
  "version": "1.0",
  "success": false,
  "code": 400,
  "message": "Invalid flow type. Must be 'login' or 'register'",
  "error_type": "VALIDATION_ERROR"
}

Handle OAuth Callback

GET /api/v1/auth/external/{provider}/callback

Handle OAuth callback from provider.

Authentication: Not required

Path Parameters:

Parameter Type Description
provider string Provider type (google, github, microsoft)

Query Parameters:

Parameter Description
code Authorization code from provider
state State parameter (contains flow context)
error Error code if auth failed
error_description Human-readable error description

Success Response (200 - Login Flow):

{
  "version": "1.0",
  "success": true,
  "code": 200,
  "data": {
    "token": "gatehouse-jwt-token...",
    "expires_in": 86400,
    "token_type": "Bearer",
    "user": {
      "id": "550e8400-e29b-41d4-a716-446655440004",
      "email": "user@example.com",
      "full_name": "John Doe",
      "organization_id": "550e8400-e29b-41d4-a716-446655440001"
    }
  },
  "message": "Login successful"
}

Success Response (200 - Register Flow):

{
  "version": "1.0",
  "success": true,
  "code": 200,
  "data": {
    "token": "gatehouse-jwt-token...",
    "expires_in": 86400,
    "token_type": "Bearer",
    "user": {
      "id": "550e8400-e29b-41d4-a716-446655440005",
      "email": "newuser@gmail.com",
      "full_name": "New User",
      "organization_id": "550e8400-e29b-41d4-a716-446655440001"
    }
  },
  "message": "Registration successful"
}

Success Response (200 - Link Flow):

{
  "version": "1.0",
  "success": true,
  "code": 200,
  "data": {
    "linked_account": {
      "id": "550e8400-e29b-41d4-a716-446655440006",
      "provider_type": "google",
      "provider_user_id": "123456789",
      "verified": true
    }
  },
  "message": "Account linked successfully"
}

Error Response (400 - Invalid state):

{
  "version": "1.0",
  "success": false,
  "code": 400,
  "message": "Invalid or expired OAuth state",
  "error_type": "INVALID_STATE"
}

Error Response (400 - Account not found):

{
  "version": "1.0",
  "success": false,
  "code": 400,
  "message": "No Gatehouse account matches this external account. Please register first.",
  "error_type": "ACCOUNT_NOT_FOUND"
}

Error Response (400 - Email exists):

{
  "version": "1.0",
  "success": false,
  "code": 400,
  "message": "An account with email user@gmail.com already exists. Please log in with your password and link your Google account from settings.",
  "error_type": "EMAIL_EXISTS"
}

Error Response (400 - Provider error):

{
  "version": "1.0",
  "success": false,
  "code": 400,
  "message": "User denied access",
  "error_type": "ACCESS_DENIED"
}

OAuth Flow Documentation

Account Linking Flow

1. User clicks "Connect Google" in settings
2. Frontend calls POST /api/v1/auth/external/google/link
3. API returns authorization_url and state
4. Frontend redirects user to authorization_url
5. User authenticates with Google and grants permission
6. Google redirects to /api/v1/auth/external/google/callback?code=xxx&state=yyy
7. API validates state, exchanges code for tokens, links account
8. API returns success response
9. Frontend shows confirmation

Login Flow

1. User clicks "Login with Google" on login page
2. Frontend redirects to /api/v1/auth/external/google/authorize?flow=login
3. API creates state, returns authorization_url
4. User authenticates with Google
5. Google redirects to callback with code and state
6. API validates state, exchanges code, authenticates user
7. API returns JWT token and user info
8. Frontend stores token and redirects to dashboard

Registration Flow

1. User clicks "Sign up with Google" on registration page
2. Frontend redirects to /api/v1/auth/external/google/authorize?flow=register
3. API creates state, returns authorization_url
4. User authenticates with Google
5. Google redirects to callback with code and state
6. API validates state, exchanges code, creates new user
7. API returns JWT token and user info
8. Frontend stores token and redirects to onboarding

Security Considerations

State Parameter

The OAuth state parameter provides CSRF protection and carries flow context:

  • Cryptographically random (256-bit)
  • Short-lived (10 minutes)
  • Single-use (marked as used after callback)
  • Bound to user (for link flows)
  • Bound to redirect_uri

PKCE Implementation

PKCE protects against authorization code interception attacks:

  • Code verifier: 43-128 character random string
  • Code challenge: S256 hash of verifier
  • Verifier sent in token request
  • Server validates challenge matches

Token Storage

Provider tokens are encrypted at rest:

  • Access tokens: Short-lived, minimal protection needed
  • Refresh tokens: Encrypted using Fernet symmetric encryption
  • ID tokens: Encrypted at rest
  • Encryption keys stored separately from database

Provider-Specific Configuration

Google OAuth

Endpoints:

  • Auth URL: https://accounts.google.com/o/oauth2/v2/auth
  • Token URL: https://oauth2.googleapis.com/token
  • UserInfo URL: https://www.googleapis.com/oauth2/v3/userinfo

Default Scopes:

  • openid - OpenID Connect authentication
  • profile - Basic profile information
  • email - Email address

Settings:

  • hosted_domain - Restrict to specific domain (optional)
  • access_type - Request refresh token (offline)
  • prompt - Force consent (consent)

GitHub OAuth

Endpoints:

  • Auth URL: https://github.com/login/oauth/authorize
  • Token URL: https://github.com/login/oauth/access_token
  • UserInfo URL: https://api.github.com/user

Default Scopes:

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

Microsoft OAuth

Endpoints:

  • Auth URL: https://login.microsoftonline.com/common/oauth2/v2.0/authorize
  • Token URL: https://login.microsoftonline.com/common/oauth2/v2.0/token
  • UserInfo URL: https://graph.microsoft.com/oidc/userinfo
  • JWKS URL: https://login.microsoftonline.com/common/discovery/v2.0/keys

Default Scopes:

  • openid - OpenID Connect
  • profile - User profile
  • email - Email address
  • offline_access - Required by Microsoft to return a refresh token (unlike Google which uses access_type=offline)

Azure App Registration steps

  1. Go to Azure PortalApp registrationsNew registration
  2. Under "Supported account types" choose the option that matches your use case (see table above)
  3. Set Redirect URI (Web platform) to: https://<your-api-host>/api/v1/auth/external/microsoft/callback
  4. Under Certificates & secretsNew client secret — copy the Value (not the Secret ID)
  5. Under API permissionsAdd a permissionMicrosoft GraphDelegated: add openid, profile, email, offline_access
  6. Configure Gatehouse:
    # Multi-tenant (work + personal accounts):
    MICROSOFT_CLIENT_ID=<Application (client) ID> \
    MICROSOFT_CLIENT_SECRET=<client secret value> \
    python scripts/configure_oauth_provider.py create microsoft \
        --redirect-url "https://<your-api-host>/api/v1/auth/external/microsoft/callback"
    
    # Work/school accounts only (replace with your tenant ID for single-org):
    MICROSOFT_CLIENT_ID=<Application (client) ID> \
    MICROSOFT_CLIENT_SECRET=<client secret value> \
    python scripts/configure_oauth_provider.py create microsoft \
        --tenant-id organizations \
        --redirect-url "https://<your-api-host>/api/v1/auth/external/microsoft/callback"
    

Behaviour notes:

  • Microsoft is a confidential client — PKCE is not used (the client secret authenticates the app).
  • The email_verified claim is implicitly true for all Azure AD accounts; Gatehouse defaults it to true when Microsoft omits it.
  • prompt=select_account is sent by default so users can choose between multiple signed-in Microsoft accounts.

Rate Limiting

Endpoint Limit Window
/authorize 10 per minute
/callback 20 per minute
/link 5 per minute
/unlink 10 per minute

Version History

Version Date Changes
1.0 2024-01-20 Initial release

Document Version: 1.0 Last Updated: 2024-01-20 Gatehouse Identity Platform