Skip to main content
OAuth2 authentication allows users to sign in using their existing accounts from popular providers like Google, GitHub, Facebook, and more.

Configuration

You can enable OAuth2 authentication in your auth collection settings:
type OAuth2Config struct {
    Providers    []OAuth2ProviderConfig `json:"providers"`
    MappedFields OAuth2KnownFields      `json:"mappedFields"`
    Enabled      bool                   `json:"enabled"`
}

Provider configuration

Each OAuth2 provider requires specific configuration:
type OAuth2ProviderConfig struct {
    Name         string         `json:"name"`          // Provider name (e.g., "google", "github")
    ClientId     string         `json:"clientId"`      // OAuth2 client ID
    ClientSecret string         `json:"clientSecret"`  // OAuth2 client secret
    AuthURL      string         `json:"authURL"`       // Authorization URL (optional)
    TokenURL     string         `json:"tokenURL"`      // Token exchange URL (optional)
    UserInfoURL  string         `json:"userInfoURL"`   // User info URL (optional)
    DisplayName  string         `json:"displayName"`   // Custom display name (optional)
    PKCE         *bool          `json:"pkce"`          // Override PKCE setting (optional)
    Extra        map[string]any `json:"extra"`         // Additional provider options
}
For standard providers (Google, GitHub, etc.), you only need to provide the name, clientId, and clientSecret. The URLs are automatically configured.

Supported providers

PocketBase supports 15+ OAuth2 providers out of the box:
  • Google
  • GitHub
  • Facebook
  • GitLab
  • Discord
  • Twitter
  • Microsoft
  • Spotify
  • Kakao
  • Twitch
  • Strava
  • Gitee
  • LiveChat
  • Gitea
  • OIDC (OpenID Connect)
  • Apple
  • Instagram
  • VK
  • Yandex
  • Patreon

OAuth2 flow

The OAuth2 authentication process follows these steps:
1

Get auth methods

Retrieve available OAuth2 providers and their authorization URLs.
curl http://localhost:8090/api/collections/users/auth-methods
Response:
{
  "oauth2": {
    "enabled": true,
    "providers": [
      {
        "name": "google",
        "displayName": "Google",
        "state": "abc123random",
        "authURL": "https://accounts.google.com/o/oauth2/auth?...",
        "codeVerifier": "...",
        "codeChallenge": "...",
        "codeChallengeMethod": "S256"
      }
    ]
  },
  "password": { "enabled": true, "identityFields": ["email"] },
  "otp": { "enabled": false, "duration": 0 },
  "mfa": { "enabled": false, "duration": 0 }
}
2

Redirect to provider

Redirect the user to the provider’s authorization URL with your redirect URI appended.
// The authURL needs a redirect_uri parameter
const redirectURL = 'https://yourdomain.com/auth/callback';
const authURL = provider.authURL + encodeURIComponent(redirectURL);
window.location.href = authURL;
3

Handle callback

After user authorizes, the provider redirects back with an authorization code.
// Extract code from URL
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
4

Exchange code for token

Send the authorization code to PocketBase to complete authentication.
curl -X POST http://localhost:8090/api/collections/users/auth-with-oauth2 \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "google",
    "code": "AUTHORIZATION_CODE",
    "codeVerifier": "CODE_VERIFIER_FROM_STEP_1",
    "redirectURL": "https://yourdomain.com/auth/callback",
    "createData": {}
  }'
5

Receive auth token

PocketBase returns the auth token and user record.
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "record": {
    "id": "RECORD_ID",
    "email": "user@example.com",
    "verified": true,
    ...
  },
  "meta": {
    "id": "oauth2_user_id",
    "name": "John Doe",
    "username": "johndoe",
    "email": "user@example.com",
    "avatarURL": "https://...",
    "isNew": true
  }
}

Authentication endpoint

POST /api/collections/{collection}/auth-with-oauth2
curl -X POST http://localhost:8090/api/collections/users/auth-with-oauth2 \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "google",
    "code": "4/0AWtgzh7...",
    "codeVerifier": "M6H2xWGzJ...",
    "redirectURL": "http://localhost:3000/auth/callback"
  }'
Request body:
type recordOAuth2LoginForm struct {
    Provider     string         `json:"provider"`     // Required: provider name
    Code         string         `json:"code"`         // Required: authorization code
    CodeVerifier string         `json:"codeVerifier"` // Required for PKCE
    RedirectURL  string         `json:"redirectURL"`  // Required: same as initial request
    CreateData   map[string]any `json:"createData"`   // Optional: additional data for new users
}

Field mapping

You can map OAuth2 user data to your collection fields:
type OAuth2KnownFields struct {
    Id        string `json:"id"`        // Map OAuth2 user ID
    Name      string `json:"name"`      // Map OAuth2 user name
    Username  string `json:"username"`  // Map OAuth2 username
    AvatarURL string `json:"avatarURL"` // Map OAuth2 avatar URL
}
Example:
collection.OAuth2.MappedFields.Name = "fullName"
collection.OAuth2.MappedFields.Username = "handle"
collection.OAuth2.MappedFields.AvatarURL = "profilePicture"
When a new user signs up via OAuth2, PocketBase automatically populates these fields:
// From apis/record_auth_with_oauth2.go:262
if _, ok := payload[e.Collection.OAuth2.MappedFields.Name]; !ok && 
   e.Collection.OAuth2.MappedFields.Name != "" {
    payload[e.Collection.OAuth2.MappedFields.Name] = e.OAuth2User.Name
}
If the mapped field for avatarURL is a file field, PocketBase automatically downloads the avatar image. Otherwise, it stores the URL as a string.

New user creation

When a user authenticates with OAuth2 for the first time, PocketBase creates a new record. You can provide additional data:
await pb.collection('users').authWithOAuth2Code(
  'google',
  code,
  codeVerifier,
  redirectURL,
  {
    // Additional fields for new user creation
    role: 'member',
    preferences: { theme: 'dark' }
  }
);
The implementation handles new user creation:
// From apis/record_auth_with_oauth2.go:250
payload := maps.Clone(e.CreateData)
if payload == nil {
    payload = map[string]any{}
}

// Assign OAuth2 user email if not submitted
if v, _ := payload[core.FieldNameEmail].(string); v == "" {
    payload[core.FieldNameEmail] = e.OAuth2User.Email
}

// Map known fields
if _, ok := payload[e.Collection.OAuth2.MappedFields.Id]; !ok && 
   e.Collection.OAuth2.MappedFields.Id != "" {
    payload[e.Collection.OAuth2.MappedFields.Id] = e.OAuth2User.Id
}

PKCE (Proof Key for Code Exchange)

PocketBase supports PKCE for enhanced security. PKCE is automatically enabled for providers that support it:
// From apis/record_auth_methods.go:147
if provider.PKCE() {
    info.CodeVerifier = security.RandomString(43)
    info.CodeChallenge = security.S256Challenge(info.CodeVerifier)
    info.CodeChallengeMethod = "S256"
    urlOpts = append(urlOpts,
        oauth2.SetAuthURLParam("code_challenge", info.CodeChallenge),
        oauth2.SetAuthURLParam("code_challenge_method", info.CodeChallengeMethod),
    )
}
You can override the default PKCE setting for any provider using the pkce field in the provider configuration.

Existing user linking

When a user authenticates with OAuth2, PocketBase tries to link the OAuth2 account to an existing user in this order:
1

Check for existing OAuth2 link

Search for an existing ExternalAuth record with the same provider and provider ID.
2

Use authenticated user

If the user is already authenticated (e.g., linking additional OAuth2 account), use the current auth record.
3

Match by email

If the OAuth2 user has an email, search for an auth record with that email.
4

Create new user

If no existing user is found, create a new auth record.
// From apis/record_auth_with_oauth2.go:120
switch {
case err == nil && externalAuthRel != nil:
    authRecord, err = e.App.FindRecordById(form.collection, externalAuthRel.RecordRef())
case fallbackAuthRecord != nil && fallbackAuthRecord.Collection().Id == form.collection.Id:
    authRecord = fallbackAuthRecord
case authUser.Email != "":
    authRecord, err = e.App.FindAuthRecordByEmail(form.collection.Id, authUser.Email)
}

Email verification

OAuth2 authenticated users are automatically verified if their email matches the OAuth2 provider’s email:
// From apis/record_auth_with_oauth2.go:306
if e.Record.Email() == e.OAuth2User.Email && !e.Record.Verified() {
    e.Record.SetVerified(true)
    if err := txApp.Save(e.Record); err != nil {
        return err
    }
}

Provider-specific notes

Apple

Apple returns the user’s name only during the first authorization. PocketBase handles this by storing the name temporarily:
// From apis/record_auth_with_oauth2.go:96
if form.Provider == auth.NameApple && authUser.Name == "" {
    nameKey := oauth2RedirectAppleNameStoreKeyPrefix + form.Code
    name, ok := e.App.Store().Get(nameKey).(string)
    if ok {
        e.App.Store().Remove(nameKey)
        authUser.Name = name
    }
}
Apple also uses response_mode=form_post:
// From apis/record_auth_methods.go:143
case auth.NameApple:
    urlOpts = append(urlOpts, 
        oauth2.SetAuthURLParam("response_mode", "form_post"))

LinkedIn OIDC

Some providers like LinkedIn OIDC may require manual PKCE adjustment:
// Disable PKCE for LinkedIn if needed
pkce := false
providerConfig.PKCE = &pkce

Security considerations

Always validate the redirectURL to prevent authorization code interception attacks. Only allow URLs from your application’s domain.

Best practices

  1. Use HTTPS: Always use HTTPS for redirect URLs in production.
  2. Validate state parameter: Although PocketBase handles this internally, ensure your client validates the state parameter to prevent CSRF attacks.
  3. Store secrets securely: Never expose OAuth2 client secrets in client-side code.
  4. Limit provider scopes: Only request the minimum required OAuth2 scopes.
  5. Handle email conflicts: Consider what happens when an OAuth2 email matches an existing password-authenticated user.

Custom OAuth2 hooks

You can customize OAuth2 authentication behavior:
app.OnRecordAuthWithOAuth2Request().Bind(&hook.Handler[*core.RecordAuthWithOAuth2RequestEvent]{
    Func: func(e *core.RecordAuthWithOAuth2RequestEvent) error {
        // Add custom fields for new users
        if e.IsNewRecord {
            if e.CreateData == nil {
                e.CreateData = make(map[string]any)
            }
            e.CreateData["role"] = "oauth2_user"
            e.CreateData["verified"] = true
        }
        
        // Log OAuth2 authentications
        log.Printf("OAuth2 auth: %s via %s", 
            e.OAuth2User.Email, e.ProviderName)
        
        return e.Next()
    },
})

OAuth2 redirect handler

PocketBase provides a global OAuth2 redirect handler for subscription redirects:
// From apis/record_auth.go:11
rg.GET("/oauth2-redirect", oauth2SubscriptionRedirect).Bind(
    SkipSuccessActivityLog(),
)
rg.POST("/oauth2-redirect", oauth2SubscriptionRedirect).Bind(
    SkipSuccessActivityLog(),
)
This endpoint handles OAuth2 callbacks and forwards them to your application.

Email/Password auth

Set up traditional password authentication

MFA

Require multiple auth methods

OTP authentication

Enable one-time password login

API rules

Configure access control