OAuth

Better Auth Ruby supports built-in social providers and generic OAuth through plugins.

If a provider is not built in, use BetterAuth::Plugins.generic_oauth.

Configuring Social Providers

auth = BetterAuth.auth(
  base_url: "https://app.example.com/api/auth",
  secret: ENV.fetch("BETTER_AUTH_SECRET"),
  social_providers: {
    github: BetterAuth::SocialProviders.github(
      client_id: ENV.fetch("GITHUB_CLIENT_ID"),
      client_secret: ENV.fetch("GITHUB_CLIENT_SECRET")
    ),
    google: BetterAuth::SocialProviders.google(
      client_id: ENV.fetch("GOOGLE_CLIENT_ID"),
      client_secret: ENV.fetch("GOOGLE_CLIENT_SECRET")
    )
  }
)

Usage

Sign In

Start an OAuth flow:

result = auth.api.sign_in_social(
  body: {
    provider: "github",
    callbackURL: "/dashboard",
    errorCallbackURL: "/login",
    disableRedirect: true
  }
)

redirect_to result[:url]

Browsers can call /sign-in/social and follow the returned provider URL.

Link a social account to the current user:

result = auth.api.link_social(
  headers: {"cookie" => cookie},
  body: {
    provider: "github",
    callbackURL: "/settings/accounts",
    scopes: ["repo"]
  }
)

redirect_to result[:url]

Ruby also supports native or ID-token linking for providers that expose verify_id_token.

Get Access Token

token = auth.api.get_access_token(
  headers: {"cookie" => cookie},
  body: {
    providerId: "github"
  }
)

If the stored token is expired and the provider has a refresh callback, Better Auth refreshes and persists it.

Get Account Info Provided By The Provider

info = auth.api.account_info(
  headers: {"cookie" => cookie},
  query: {
    accountId: "provider-account-id"
  }
)

Requesting Additional Scopes

Use link_social with the same provider and additional scopes:

auth.api.link_social(
  headers: {"cookie" => cookie},
  body: {
    provider: "google",
    scopes: ["https://www.googleapis.com/auth/drive.readonly"],
    callbackURL: "/settings/integrations"
  }
)

Provider defaults can also include extra scopes:

BetterAuth::SocialProviders.github(
  client_id: ENV.fetch("GITHUB_CLIENT_ID"),
  client_secret: ENV.fetch("GITHUB_CLIENT_SECRET"),
  scopes: ["user:email", "read:user", "repo"]
)

Passing Additional Data Through OAuth Flow

Pass a hash in additionalData. Reserved OAuth state keys are filtered out.

auth.api.sign_in_social(
  body: {
    provider: "github",
    callbackURL: "/dashboard",
    additionalData: {
      source: "pricing-page",
      plan: "pro"
    }
  }
)

Accessing Additional Data In Hooks

Hooks can inspect request body and callback state depending on which route they run around. Validate any additional data before using it.

hooks: {
  after: [
    {
      matcher: ->(ctx) { ctx.path == "/callback/github" },
      handler: ->(ctx) { Rails.logger.info(ctx.query["state"]) }
    }
  ]
}

Handling Providers Without Email

OAuth providers that do not return email cannot create or link a user directly. Use provider mapping to synthesize a placeholder email only when your app can safely treat it as non-contact identity.

Synthesize A Placeholder Email With map_profile_to_user

BetterAuth::SocialProviders.discord(
  client_id: ENV.fetch("DISCORD_CLIENT_ID"),
  client_secret: ENV.fetch("DISCORD_CLIENT_SECRET"),
  map_profile_to_user: lambda do |profile|
    id = profile["id"]
    {
      "email" => "discord-#{id}@users.example.invalid",
      "emailVerified" => false
    }
  end
)

Use a domain you control or a reserved suffix such as .invalid.

Provider-Specific Notes

Some providers return email only on first consent, only with specific scopes, or only for verified accounts. Check the provider class and scopes for your provider before relying on email.

Provider Options

clientId

Use Ruby client_id; camelCase provider options are normalized where applicable.

BetterAuth::SocialProviders.github(client_id: "...", client_secret: "...")

scope

Use scope or scopes.

BetterAuth::SocialProviders.github(
  client_id: "...",
  client_secret: "...",
  scope: ["read:user"]
)

redirectURI

Override the redirect URI when required by the provider:

BetterAuth::SocialProviders.github(
  client_id: "...",
  client_secret: "...",
  redirect_uri: "https://app.example.com/api/auth/callback/github"
)

disableSignUp

BetterAuth::SocialProviders.github(
  client_id: "...",
  client_secret: "...",
  disable_sign_up: true
)

disableIdTokenSignIn

BetterAuth::SocialProviders.google(
  client_id: "...",
  client_secret: "...",
  disable_id_token_sign_in: true
)

verifyIdToken

Provide custom ID token verification:

BetterAuth::SocialProviders.google(
  client_id: "...",
  client_secret: "...",
  verify_id_token: ->(token, nonce = nil) { MyVerifier.valid?(token, nonce) }
)

overrideUserInfoOnSignIn

Update profile fields from provider data when an existing user signs in:

BetterAuth::SocialProviders.github(
  client_id: "...",
  client_secret: "...",
  override_user_info_on_sign_in: true
)

mapProfileToUser

BetterAuth::SocialProviders.github(
  client_id: "...",
  client_secret: "...",
  map_profile_to_user: ->(profile) { {"name" => profile["login"]} }
)

refreshAccessToken

Provide a custom refresh callback:

BetterAuth::SocialProviders.github(
  client_id: "...",
  client_secret: "...",
  refresh_access_token: lambda do |refresh_token|
    MyOAuth.refresh_github_token(refresh_token)
  end
)

clientKey

Some providers require a client key in addition to client ID and secret. Pass client_key when the provider supports it.

getUserInfo

Override user-info fetching:

BetterAuth::SocialProviders.github(
  client_id: "...",
  client_secret: "...",
  get_user_info: lambda do |tokens|
    profile = GitHubClient.new(tokens["accessToken"]).user
    {
      user: {
        "id" => profile.id,
        "email" => profile.email,
        "name" => profile.name,
        "emailVerified" => true
      },
      data: profile
    }
  end
)

disableImplicitSignUp

BetterAuth::SocialProviders.github(
  client_id: "...",
  client_secret: "...",
  disable_implicit_sign_up: true
)

Users must explicitly request sign-up for that provider.

prompt

BetterAuth::SocialProviders.google(
  client_id: "...",
  client_secret: "...",
  prompt: "select_account"
)

responseMode

Use provider-specific options when a provider class accepts response mode settings.

disableDefaultScope

BetterAuth::SocialProviders.github(
  client_id: "...",
  client_secret: "...",
  disable_default_scope: true,
  scopes: ["read:user"]
)

Other Provider Configurations

Provider classes normalize common upstream option names to Ruby snake_case. Inspect the specific provider class for provider-specific settings such as tenant IDs, app bundle identifiers, profile photo settings, and endpoint overrides.