Documentation
¶
Overview ¶
Package oauth signs users in with identity providers such as Google and GitHub. It owns auth_account, one row per linked provider account.
state and PKCE are always on. The state is a single-use core verification token that is also bound to the browser through the flow effect, so a callback is accepted only in the browser that started it — the defence against login CSRF. Linking to an existing account happens automatically only when both sides are verified; otherwise the user signs in first and links from settings.
Index ¶
- Constants
- Variables
- func AppleClientSecret(teamID, clientID, keyID string, key *ecdsa.PrivateKey) (func(ctx context.Context) (string, error), error)
- func ApplePostedUser(raw string, p *Profile)
- func AppleScopes() []string
- func AtlassianProfile(u string) ...
- func CallbackEndpoint(provider string) auth.EndpointID
- func ClientAssertion(clientID, kid string, key crypto.Signer) (func(ctx context.Context, tokenURL string) (string, error), error)
- func CloudflareProfile(u string) ...
- func DiscordProfile(u string) ...
- func DropboxProfile(u string) ...
- func DubProfile(u string) ...
- func FacebookProfile(u string) ...
- func FigmaProfile(u string) ...
- func FormPostEndpoint(provider string) auth.EndpointID
- func GitHubProfile(base string) ...
- func GoogleProfile(u string) ...
- func GoogleProfileFor(u, hostedDomain string) ...
- func GumroadProfile(u string) ...
- func HubSpotProfile(base string) ...
- func IDTokenEndpoint(provider string) auth.EndpointID
- func InstagramProfile(u string) ...
- func KakaoProfile(u string) ...
- func KickProfile(u string) ...
- func LinearProfile(u string) ...
- func LogoutEndpoint(provider string) auth.EndpointID
- func MicrosoftPhoto(...) (...)
- func NaverProfile(u string) ...
- func NotionProfile(u string) ...
- func PKCEChallenge(verifier string) string
- func ParseAppleKey(p8 []byte) (*ecdsa.PrivateKey, error)
- func PatreonProfile(u string) ...
- func RedditProfile(u, userAgent string) ...
- func SnapchatProfile(u string) ...
- func SpotifyProfile(u string) ...
- func StartEndpoint(provider string) auth.EndpointID
- func TikTokProfile(u string) ...
- func UserInfoProfile(u, subjectClaim string) ...
- func VKProfile(u, clientID string) ...
- func ValidateProvider(prov Provider) error
- func WeChatProfile(u string) func(ctx context.Context, hc *http.Client, ts TokenSet) (Profile, error)
- func WithCallbackValues(ctx context.Context, v CallbackValues) context.Context
- func XProfile(u string) ...
- func YandexProfile(u string) ...
- func ZoomProfile(u string) ...
- type AccessTokenRequest
- type AccessTokenResult
- type Account
- type AccountAction
- type AccountsResult
- type CallbackParam
- type CallbackRequest
- type CallbackResult
- type CallbackValues
- type Discoverer
- type Endpoints
- type FormPostRequest
- type FormPostResult
- type FormPoster
- type IDTokenClaim
- type IDTokenConfig
- type IDTokenReplayGuard
- type IDTokenReplayGuardFunc
- type IDTokenRequest
- type IDTokenVerifier
- type ImportAccount
- type InitiatedExchanger
- type IssuerChecker
- type Labeler
- type Loader
- type LogoutRequest
- type LogoutResult
- type Logouter
- type MicrosoftPhotoSize
- type OAuth2
- func Apple(clientID string, secret func(ctx context.Context) (string, error), ...) *OAuth2
- func AppleWithEmail(clientID string, secret func(ctx context.Context) (string, error), ...) *OAuth2
- func Atlassian(clientID, clientSecret string) *OAuth2
- func Cloudflare(clientID, clientSecret string) *OAuth2
- func Discord(clientID, clientSecret string) *OAuth2
- func Dropbox(clientID, clientSecret string) *OAuth2
- func Dub(clientID, clientSecret string) *OAuth2
- func Facebook(clientID, clientSecret string) *OAuth2
- func Figma(clientID, clientSecret string) *OAuth2
- func GitHub(clientID, clientSecret string) *OAuth2
- func Google(clientID, clientSecret string) *OAuth2
- func Gumroad(clientID, clientSecret string) *OAuth2
- func HubSpot(clientID, clientSecret string) *OAuth2
- func Instagram(appID, appSecret string) *OAuth2
- func Kakao(clientID, clientSecret string) *OAuth2
- func Kick(clientID, clientSecret string) *OAuth2
- func Linear(clientID, clientSecret string) *OAuth2
- func Microsoft(tenant, clientID, clientSecret string) *OAuth2
- func Naver(clientID, clientSecret string) *OAuth2
- func Notion(clientID, clientSecret string) *OAuth2
- func Patreon(clientID, clientSecret string) *OAuth2
- func PayPal(clientID, clientSecret string, sandbox bool) *OAuth2
- func Polar(clientID, clientSecret string) *OAuth2
- func Reddit(clientID, clientSecret, userAgent string) *OAuth2
- func Snapchat(clientID, clientSecret string) *OAuth2
- func Spotify(clientID, clientSecret string) *OAuth2
- func TikTok(clientKey, clientSecret string) *OAuth2
- func VK(clientID, clientSecret string) *OAuth2
- func Vercel(clientID, clientSecret string) *OAuth2
- func WeChat(appID, appSecret, lang string) *OAuth2
- func X(clientID, clientSecret string) *OAuth2
- func Yandex(clientID, clientSecret string) *OAuth2
- func Zoom(clientID, clientSecret string) *OAuth2
- func (o *OAuth2) ApplyPosted(raw string, p *Profile)
- func (o *OAuth2) AuthCodeURL(state, challenge, redirectURI string) string
- func (o *OAuth2) AuthCodeURLWithParams(state, challenge, redirectURI string, rp RequestParams) string
- func (o *OAuth2) EndSession(context.Context, *http.Client) (string, error)
- func (o *OAuth2) Exchange(ctx context.Context, hc *http.Client, code, verifier, redirectURI string) (Profile, error)
- func (o *OAuth2) ExchangeInitiated(ctx context.Context, hc *http.Client, code, redirectURI string) (Profile, error)
- func (o *OAuth2) ExpectedIssuer() string
- func (o *OAuth2) FetchProfileWith(ctx context.Context, hc *http.Client, ts TokenSet) (Profile, error)
- func (o *OAuth2) Label() string
- func (o *OAuth2) Load(ctx context.Context, hc *http.Client) error
- func (o *OAuth2) Name() string
- func (o *OAuth2) OptionalParamList() []string
- func (o *OAuth2) OptionalScopeList() []string
- func (o *OAuth2) PostsBack() bool
- func (o *OAuth2) Refresh(ctx context.Context, hc *http.Client, refreshToken auth.Token) (TokenSet, error)
- func (o *OAuth2) RefreshWithParams(ctx context.Context, hc *http.Client, refreshToken auth.Token, ...) (TokenSet, error)
- func (o *OAuth2) Revoke(ctx context.Context, hc *http.Client, ts TokenSet) error
- func (o *OAuth2) VerifyIDToken(ctx context.Context, hc *http.Client, token, nonce string) (Profile, error)
- type OIDCProvider
- func Auth0(domain, clientID, clientSecret string) *OIDCProvider
- func Cognito(region, userPoolID, clientID, clientSecret string) *OIDCProvider
- func GitLab(issuer, clientID, clientSecret string) *OIDCProvider
- func HuggingFace(clientID, clientSecret string) *OIDCProvider
- func Keycloak(issuer, clientID, clientSecret string) *OIDCProvider
- func LINE(clientID, clientSecret string) *OIDCProvider
- func LinkedIn(clientID, clientSecret string) *OIDCProvider
- func OIDC(name, issuer, clientID, clientSecret string) *OIDCProvider
- func Okta(issuer, clientID, clientSecret string) *OIDCProvider
- func Paybin(clientID, clientSecret string) *OIDCProvider
- func Railway(clientID, clientSecret string) *OIDCProvider
- func Roblox(clientID, clientSecret string) *OIDCProvider
- func Salesforce(issuer, clientID, clientSecret string) *OIDCProvider
- func Slack(clientID, clientSecret string) *OIDCProvider
- func Twitch(clientID, clientSecret string) *OIDCProvider
- func Zitadel(issuer, clientID, clientSecret string) *OIDCProvider
- func Zoho(region ZohoRegion, clientID, clientSecret string) *OIDCProvider
- func (o *OIDCProvider) AuthCodeURL(state, challenge, redirectURI string) string
- func (o *OIDCProvider) AuthCodeURLWithParams(state, challenge, redirectURI string, rp RequestParams) string
- func (o *OIDCProvider) Discover(ctx context.Context, hc *http.Client) error
- func (o *OIDCProvider) EndSession(ctx context.Context, hc *http.Client) (string, error)
- func (o *OIDCProvider) Exchange(ctx context.Context, hc *http.Client, code, verifier, redirectURI string) (Profile, error)
- func (o *OIDCProvider) ExchangeInitiated(ctx context.Context, hc *http.Client, code, redirectURI string) (Profile, error)
- func (o *OIDCProvider) ExpectedIssuer() string
- func (o *OIDCProvider) FetchProfileWith(ctx context.Context, hc *http.Client, ts TokenSet) (Profile, error)
- func (o *OIDCProvider) Label() string
- func (o *OIDCProvider) Load(ctx context.Context, hc *http.Client) error
- func (o *OIDCProvider) Name() string
- func (o *OIDCProvider) OptionalParamList() []string
- func (o *OIDCProvider) OptionalScopeList() []string
- func (o *OIDCProvider) Refresh(ctx context.Context, hc *http.Client, refreshToken auth.Token) (TokenSet, error)
- func (o *OIDCProvider) Revoke(ctx context.Context, hc *http.Client, ts TokenSet) error
- func (o *OIDCProvider) VerifyIDToken(ctx context.Context, hc *http.Client, token, nonce string) (Profile, error)
- type Option
- func AllowIDTokenReplay() Option
- func AllowProviderInitiated(provider string, more ...string) Option
- func AllowUnlinkingAll() Option
- func CallbackURL(fn func(provider string) string) Option
- func ErrorURL(path string) Option
- func ExplicitSignUp(providers ...string) Option
- func ExposeAccessTokens() Option
- func LinkUnverifiedLocalEmail() Option
- func NoImplicitLinking() Option
- func NoSignUp(providers ...string) Option
- func OnResolved(fn ResolvedHook) Option
- func PostLogoutURL(fn func(provider string) string) Option
- func RequireEmail() Option
- func RequireSameEmailOnLink() Option
- func RequireVerifiedEmail() Option
- func RestartProviderInitiated(provider string, more ...string) Option
- func SkipBrokenProviders() Option
- func SkipStateCookieCheck() Option
- func StateInCookie() Option
- func StoreTokens(key ...[]byte) Option
- func StoreTokensWith(s auth.Sealer) Option
- func TrustedProviders(providers ...string) Option
- func UpdateProfileOnSignIn() Option
- func ValidateProfile(fn ProfileHook) Option
- func VerifyEmailBeforeSignIn() Option
- func WithHTTPClient(hc *http.Client) Option
- func WithIDTokenReplayGuard(g IDTokenReplayGuard) Option
- func WithProviders(ps ...Provider) Option
- func WithServerContext(fn ServerContextFunc) Option
- func WithStateTTL(d time.Duration) Option
- type ParamRefresher
- type ParamsAllowLister
- type ParamsProvider
- type Plugin
- func (p *Plugin) Accounts(ctx context.Context, userID string) ([]Account, error)
- func (p *Plugin) Callback(ctx context.Context, provider string, in CallbackRequest) (CallbackResult, error)
- func (p *Plugin) ImportAccount(ctx context.Context, in ImportAccount) error
- func (p *Plugin) Init(a *auth.Auth) error
- func (p *Plugin) Name() string
- func (p *Plugin) Operations() []auth.Operation
- func (p *Plugin) OptionErr() error
- func (p *Plugin) Profile(ctx context.Context, userID, provider string) (Profile, error)
- func (p *Plugin) Providers() []ProviderInfo
- func (p *Plugin) RateLimits() []auth.RateRule
- func (p *Plugin) Reauthenticate(ctx context.Context, in ReauthenticateRequest) (StartResult, error)
- func (p *Plugin) ReauthenticateIDToken(ctx context.Context, in ReauthenticateIDTokenRequest) (auth.Reauthentication, error)
- func (p *Plugin) Refresh(ctx context.Context, userID, provider string) (TokenSet, error)
- func (p *Plugin) SignInCredentials(ctx context.Context, userID, exceptID string) (int, error)
- func (p *Plugin) SignInMethods() []authhttp.SignInMethod
- func (p *Plugin) SignsIn() bool
- func (p *Plugin) Start(ctx context.Context, provider string, in StartRequest) (StartResult, error)
- func (p *Plugin) Tables() []sqlb.Table
- func (p *Plugin) Tokens(ctx context.Context, userID, provider string) (TokenSet, error)
- func (p *Plugin) TypedMigrations(engine string, t auth.SQLTypes) ([]auth.Migration, error)
- func (p *Plugin) Unlink(ctx context.Context, provider string) error
- func (p *Plugin) UnlinkAccount(ctx context.Context, provider, accountID string) error
- func (p *Plugin) UserTables() []auth.UserTable
- func (p *Plugin) Validate(c *auth.Config) error
- type PostedProfile
- type Profile
- type ProfileAction
- type ProfileEvent
- type ProfileFetcher
- type ProfileHook
- type Provider
- type ProviderInfo
- type ProvidersResult
- type ReauthenticateIDTokenRequest
- type ReauthenticateRequest
- type Refresher
- type RequestAuthURLer
- type RequestParams
- type ResolvedEvent
- type ResolvedHook
- type Revoker
- type ServerContextFunc
- type StartRequest
- type StartResult
- type TokenAuth
- type TokenSet
- type UnlinkRequest
- type ZohoRegion
Constants ¶
const ( //ds:def id=appleauthurl-t2vj6kcd owner=@khanakia stability=stable AppleAuthURL = "https://appleid.apple.com/auth/authorize" //ds:def id=appletokenurl-n8wq4rzm owner=@khanakia stability=stable AppleTokenURL = "https://appleid.apple.com/auth/token" //ds:def id=applejwksurl-b5xk7pqt owner=@khanakia stability=stable AppleJWKSURL = "https://appleid.apple.com/auth/keys" // AppleIssuer is the iss of every Apple ID token and the aud of the // client-secret assertion. //ds:def id=appleissuer-m4zr9hvw owner=@khanakia stability=stable AppleIssuer = "https://appleid.apple.com" )
Apple endpoints (Sign in with Apple REST API).
const ( EndpointUnlink auth.EndpointID = "oauth.unlink" //ds:def id=endpointaccounts-j8bwb2tj owner=@khanakia stability=stable EndpointAccounts auth.EndpointID = "oauth.accounts" // EndpointLogout is the general provider logout, POST /logout with the // provider named in the body; it exists when any provider is a // Logouter, beside each one's own /<provider>/logout. //ds:def id=endpointlogout-h3wq8nzk owner=@khanakia stability=stable EndpointLogout auth.EndpointID = "oauth.logout" // EndpointProviders lists the configured providers' ids and display // names, GET /providers, public. //ds:def id=endpointproviders-p6xk2wtm owner=@khanakia stability=stable EndpointProviders auth.EndpointID = "oauth.providers" )
Operation ids. Start and callback ids are per provider: "oauth.<provider>-start" and "oauth.<provider>-callback".
const ( //ds:def id=tablenameaccount-wkmv8x3c owner=@khanakia stability=stable TableNameAccount = "auth_account" TableAliasAccount = "acc" //ds:def id=colaccountid-7gm3bq4c owner=@khanakia stability=stable ColAccountID = "id" //ds:def id=colaccountuserid-hn74aaqn owner=@khanakia stability=stable ColAccountUserID = "user_id" //ds:def id=colaccountprovider-bdjpuq2m owner=@khanakia stability=stable ColAccountProvider = "provider" //ds:def id=colaccountsubject-e25223he owner=@khanakia stability=stable ColAccountSubject = "subject" //ds:def id=colaccountemail-un48a32q owner=@khanakia stability=stable ColAccountEmail = "email" //ds:def id=colaccountcreated-r3epfyb7 owner=@khanakia stability=stable ColAccountCreated = "created_at" // The token columns are empty unless StoreTokens is set. //ds:def id=colaccountaccess-dxys2my7 owner=@khanakia stability=stable ColAccountAccess = "access_token_sealed" //ds:def id=colaccountrefresh-mjswm325 owner=@khanakia stability=stable ColAccountRefresh = "refresh_token_sealed" //ds:def id=colaccountexpires-wg2zr8ha owner=@khanakia stability=stable ColAccountExpires = "token_expires_at" // ColAccountIDToken is the sealed ID token, NULL unless StoreTokens is // set and the provider returned one; added by migration 0002. //ds:def id=colaccountidtoken-5w2kjn8p owner=@khanakia stability=stable ColAccountIDToken = "id_token_sealed" // ColAccountScope is the scope the provider said it granted at the last // exchange or refresh, ” when it did not say; migration 0003. //ds:def id=colaccountscope-q7mt3xkd owner=@khanakia stability=stable ColAccountScope = "token_scope" // ColAccountRefreshExpires is when the refresh token expires, NULL when // the provider did not say; migration 0003. //ds:def id=colaccountrefreshexpires-b4nw8ztp owner=@khanakia stability=stable ColAccountRefreshExpires = "refresh_token_expires_at" // ColAccountRefreshParams holds the sealed TokenSet.RefreshParams (VK // ID's device_id), NULL for every other provider; migration 0004. //ds:def id=colaccountrefreshparams-3nq8wk5x owner=@khanakia stability=stable ColAccountRefreshParams = "refresh_params_sealed" )
The plugin's table.
const ( // DefaultStateTTL bounds how long a user may spend at the provider. Ten // minutes covers a password manager and a second factor there; a longer // window only lengthens the life of a leaked state. //ds:def id=defaultstatettl-zsf722py owner=@khanakia stability=stable DefaultStateTTL = 10 * time.Minute // DefaultHTTPTimeout bounds each call to a provider, so a hung provider // fails the callback instead of holding the request open. DefaultHTTPTimeout = 10 * time.Second // DefaultMountPrefix is where the default callback URL assumes the auth // handler is mounted. //ds:def id=defaultmountprefix-4xrrxrkb owner=@khanakia stability=stable DefaultMountPrefix = "/auth" )
Defaults.
const ( //ds:def id=dubauthurl-p3d7kx2m owner=@khanakia stability=stable DubAuthURL = "https://app.dub.co/oauth/authorize" //ds:def id=dubtokenurl-p3d4wq8n owner=@khanakia stability=stable DubTokenURL = "https://api.dub.co/oauth/token" //ds:def id=dubuserinfourl-p3d9mx3k owner=@khanakia stability=stable DubUserInfoURL = "https://api.dub.co/oauth/userinfo" )
Dub endpoints (dub.co/docs/integrations/quickstart, read 2026-10-08), the ones @dub/better-auth's oauth option configures.
const ( //ds:def id=instagramauthurl-p3i2kw7q owner=@khanakia stability=stable InstagramAuthURL = "https://www.instagram.com/oauth/authorize" //ds:def id=instagramtokenurl-p3i8nx4t owner=@khanakia stability=stable InstagramTokenURL = "https://api.instagram.com/oauth/access_token" // InstagramMeURL is the Graph API profile of the token's account, with // the fields the preset reads. //ds:def id=instagrammeurl-p3i5qk9w owner=@khanakia stability=stable InstagramMeURL = "https://graph.instagram.com/me?fields=id,user_id,username,name,profile_picture_url,account_type" // InstagramTokenLifetime is the life of the short-lived token the code // exchange returns, which carries no expires_in (one hour, as Instagram // documents it). //ds:def id=instagramtokenlifetime-p3i3wn6k owner=@khanakia stability=stable InstagramTokenLifetime = time.Hour )
Instagram endpoints: the Instagram API with Instagram Login (developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login, read 2026-10-08), the ones better-auth-instagram configures.
const ( //ds:def id=snapchatauthurl-p3s8wk2n owner=@khanakia stability=stable SnapchatAuthURL = "https://accounts.snapchat.com/accounts/oauth2/auth" //ds:def id=snapchattokenurl-p3s3nx7q owner=@khanakia stability=stable SnapchatTokenURL = "https://accounts.snapchat.com/accounts/oauth2/token" //ds:def id=snapchatrevokeurl-p3s6qw4k owner=@khanakia stability=stable SnapchatRevokeURL = "https://accounts.snapchat.com/accounts/oauth2/revoke" //ds:def id=snapchatmeurl-p3s9kx5t owner=@khanakia stability=stable SnapchatMeURL = "https://kit.snapchat.com/v1/me" // SnapchatScopePrefix is how Snapchat names a scope: the full URL. //ds:def id=snapchatscopeprefix-p3s2wn8x owner=@khanakia stability=stable SnapchatScopePrefix = "https://auth.snapchat.com/oauth2/api/" )
Snapchat endpoints: Login Kit's server-side OAuth2 flow (developers.snap.com/snap-kit/login-kit/Tutorials/web, read 2026-10-08) and the Snap Kit /v1/me GraphQL read better-auth-snapchat uses.
const ( //ds:def id=googleauthurl-ekqypyzt owner=@khanakia stability=stable GoogleAuthURL = "https://accounts.google.com/o/oauth2/v2/auth" //ds:def id=googletokenurl-ktw4f4uv owner=@khanakia stability=stable GoogleTokenURL = "https://oauth2.googleapis.com/token" //ds:def id=googleuserinfourl-5nkm522q owner=@khanakia stability=stable GoogleUserInfoURL = "https://openidconnect.googleapis.com/v1/userinfo" // GoogleJWKSURL publishes the keys Google signs ID tokens with. //ds:def id=googlejwksurl-h4tm9wqz owner=@khanakia stability=stable GoogleJWKSURL = "https://www.googleapis.com/oauth2/v3/certs" )
Google endpoints.
const ( //ds:def id=githubauthurl-etwyp97v owner=@khanakia stability=stable GitHubAuthURL = "https://github.com/login/oauth/authorize" //ds:def id=githubtokenurl-2ww26mbk owner=@khanakia stability=stable GitHubTokenURL = "https://github.com/login/oauth/access_token" //ds:def id=githubapiurl-5sv8a6v5 owner=@khanakia stability=stable GitHubAPIURL = "https://api.github.com" )
GitHub endpoints.
const ( //ds:def id=slackissuer-k2wq8tnm owner=@khanakia stability=stable SlackIssuer = "https://slack.com" //ds:def id=linkedinissuer-x7mq3bkz owner=@khanakia stability=stable LinkedInIssuer = "https://www.linkedin.com/oauth" //ds:def id=gitlabissuer-p4nt9wqr owner=@khanakia stability=stable GitLabIssuer = "https://gitlab.com" //ds:def id=twitchissuer-v8kx2mqn owner=@khanakia stability=stable TwitchIssuer = "https://id.twitch.tv/oauth2" //ds:def id=lineissuer-h3zr7xkt owner=@khanakia stability=stable LINEIssuer = "https://access.line.me" //ds:def id=huggingfaceissuer-b6wn4pqm owner=@khanakia stability=stable HuggingFaceIssuer = "https://huggingface.co" //ds:def id=robloxissuer-t9mk5xqw owner=@khanakia stability=stable RobloxIssuer = "https://apis.roblox.com/oauth" //ds:def id=salesforceissuer-n2qt8wkr owner=@khanakia stability=stable SalesforceIssuer = "https://login.salesforce.com" // SalesforceSandboxIssuer is the issuer of Salesforce sandbox orgs. //ds:def id=salesforcesandboxissuer-m5xk3nqz owner=@khanakia stability=stable SalesforceSandboxIssuer = "https://test.salesforce.com" // PaybinIssuer and RailwayIssuer were read from their discovery // documents on 2026-10-03: both publish a userinfo endpoint, S256 and // client_secret_post; Railway also announces the RFC 9207 iss parameter. //ds:def id=paybinissuer-k8wq3xtn owner=@khanakia stability=stable PaybinIssuer = "https://idp.paybin.io" //ds:def id=railwayissuer-m4nt7xkq owner=@khanakia stability=stable RailwayIssuer = "https://backboard.railway.com" )
OpenID Connect issuers. Each preset is OIDC with the issuer's own default scopes; every one of them publishes a discovery document with a userinfo endpoint.
const ( //ds:def id=microsoftauthurlformat-x4kn8wqt owner=@khanakia stability=stable MicrosoftAuthURLFormat = "https://login.microsoftonline.com/%s/oauth2/v2.0/authorize" //ds:def id=microsofttokenurlformat-m7wq2xkn owner=@khanakia stability=stable MicrosoftTokenURLFormat = "https://login.microsoftonline.com/%s/oauth2/v2.0/token" //ds:def id=microsoftjwksurlformat-t2kq9wnx owner=@khanakia stability=stable MicrosoftJWKSURLFormat = "https://login.microsoftonline.com/%s/discovery/v2.0/keys" //ds:def id=microsoftissuerformat-k8xn3wqm owner=@khanakia stability=stable MicrosoftIssuerFormat = "https://login.microsoftonline.com/%s/v2.0" //ds:def id=microsoftuserinfourl-w3qt7xkn owner=@khanakia stability=stable MicrosoftUserInfoURL = "https://graph.microsoft.com/oidc/userinfo" )
Microsoft Entra ID endpoints. The tenant is a tenant id or domain, or one of the multi-tenant aliases common, organizations and consumers.
const ( //ds:def id=discordauthurl-n8kq4wxt owner=@khanakia stability=stable DiscordAuthURL = "https://discord.com/oauth2/authorize" //ds:def id=discordtokenurl-q3wn7xkm owner=@khanakia stability=stable DiscordTokenURL = "https://discord.com/api/oauth2/token" //ds:def id=discorduserurl-x6tk2wqn owner=@khanakia stability=stable DiscordUserURL = "https://discord.com/api/users/@me" )
Discord endpoints.
const ( //ds:def id=facebookauthurl-w7kn3xqt owner=@khanakia stability=stable FacebookAuthURL = "https://www.facebook.com/v24.0/dialog/oauth" //ds:def id=facebooktokenurl-t4xq8wkn owner=@khanakia stability=stable FacebookTokenURL = "https://graph.facebook.com/v24.0/oauth/access_token" //ds:def id=facebookprofileurl-m2wk9xqn owner=@khanakia stability=stable FacebookProfileURL = "https://graph.facebook.com/v24.0/me?fields=id,name,email,picture.type(large)" )
Facebook endpoints (Graph API v24.0).
const ( //ds:def id=spotifyauthurl-k3wq7xnt owner=@khanakia stability=stable SpotifyAuthURL = "https://accounts.spotify.com/authorize" //ds:def id=spotifytokenurl-x9kn2wqt owner=@khanakia stability=stable SpotifyTokenURL = "https://accounts.spotify.com/api/token" //ds:def id=spotifyprofileurl-n6tq4wkx owner=@khanakia stability=stable SpotifyProfileURL = "https://api.spotify.com/v1/me" )
Spotify endpoints.
const ( //ds:def id=dropboxauthurl-t7kq3wxn owner=@khanakia stability=stable DropboxAuthURL = "https://www.dropbox.com/oauth2/authorize" //ds:def id=dropboxtokenurl-m9xn8wqk owner=@khanakia stability=stable DropboxTokenURL = "https://api.dropboxapi.com/oauth2/token" //ds:def id=dropboxprofileurl-q2wt4xkn owner=@khanakia stability=stable DropboxProfileURL = "https://api.dropboxapi.com/2/users/get_current_account" )
Dropbox endpoints.
const ( //ds:def id=xauthurl-k6wn2xqt owner=@khanakia stability=stable XAuthURL = "https://x.com/i/oauth2/authorize" //ds:def id=xtokenurl-n4kq9wxt owner=@khanakia stability=stable XTokenURL = "https://api.x.com/2/oauth2/token" //ds:def id=xprofileurl-w8tk3xqn owner=@khanakia stability=stable XProfileURL = "https://api.x.com/2/users/me?user.fields=profile_image_url,confirmed_email" )
X (Twitter) endpoints.
const ( //ds:def id=redditauthurl-t3xk7wqn owner=@khanakia stability=stable RedditAuthURL = "https://www.reddit.com/api/v1/authorize" //ds:def id=reddittokenurl-m8wq4xkt owner=@khanakia stability=stable RedditTokenURL = "https://www.reddit.com/api/v1/access_token" //ds:def id=redditprofileurl-x2nk9wqt owner=@khanakia stability=stable RedditProfileURL = "https://oauth.reddit.com/api/v1/me" )
Reddit endpoints.
const ( //ds:def id=atlassianauthurl-k4nq8wxt owner=@khanakia stability=stable AtlassianAuthURL = "https://auth.atlassian.com/authorize?audience=api.atlassian.com&prompt=consent" //ds:def id=atlassiantokenurl-q9xk3wnt owner=@khanakia stability=stable AtlassianTokenURL = "https://auth.atlassian.com/oauth/token" //ds:def id=atlassianprofileurl-w2tk7xqn owner=@khanakia stability=stable AtlassianProfileURL = "https://api.atlassian.com/me" )
Atlassian endpoints.
const ( //ds:def id=zoomauthurl-x8kq2wnt owner=@khanakia stability=stable ZoomAuthURL = "https://zoom.us/oauth/authorize" //ds:def id=zoomtokenurl-t5wn9xkq owner=@khanakia stability=stable ZoomTokenURL = "https://zoom.us/oauth/token" //ds:def id=zoomprofileurl-m3xk6wqt owner=@khanakia stability=stable ZoomProfileURL = "https://api.zoom.us/v2/users/me" )
Zoom endpoints.
const ( //ds:def id=figmaauthurl-w9kn4xqt owner=@khanakia stability=stable FigmaAuthURL = "https://www.figma.com/oauth" //ds:def id=figmatokenurl-k2xq8wnt owner=@khanakia stability=stable FigmaTokenURL = "https://api.figma.com/v1/oauth/token" //ds:def id=figmaprofileurl-n6wt3xkq owner=@khanakia stability=stable FigmaProfileURL = "https://api.figma.com/v1/me" )
Figma endpoints.
const ( //ds:def id=kakaoauthurl-x4wq7knt owner=@khanakia stability=stable KakaoAuthURL = "https://kauth.kakao.com/oauth/authorize" //ds:def id=kakaotokenurl-m8kn3xqw owner=@khanakia stability=stable KakaoTokenURL = "https://kauth.kakao.com/oauth/token" //ds:def id=kakaoprofileurl-q2tx9wkn owner=@khanakia stability=stable KakaoProfileURL = "https://kapi.kakao.com/v2/user/me" )
Kakao endpoints.
const ( NaverAuthURL = "https://nid.naver.com/oauth2.0/authorize" NaverTokenURL = "https://nid.naver.com/oauth2.0/token" NaverProfileURL = "https://openapi.naver.com/v1/nid/me" )
Naver endpoints.
const ( //ds:def id=kickauthurl-k8wn4xqt owner=@khanakia stability=stable KickAuthURL = "https://id.kick.com/oauth/authorize" //ds:def id=kicktokenurl-x3kq7wnt owner=@khanakia stability=stable KickTokenURL = "https://id.kick.com/oauth/token" //ds:def id=kickprofileurl-m7tx2wkq owner=@khanakia stability=stable KickProfileURL = "https://api.kick.com/public/v1/users" )
Kick endpoints.
const ( //ds:def id=linearauthurl-w5kq3xnt owner=@khanakia stability=stable LinearAuthURL = "https://linear.app/oauth/authorize" //ds:def id=lineartokenurl-n8xk7wqt owner=@khanakia stability=stable LinearTokenURL = "https://api.linear.app/oauth/token" //ds:def id=lineargraphqlurl-q4wn2xkt owner=@khanakia stability=stable LinearGraphQLURL = "https://api.linear.app/graphql" )
Linear endpoints.
const ( //ds:def id=cloudflareauthurl-k3nq8wxt owner=@khanakia stability=stable CloudflareAuthURL = "https://dash.cloudflare.com/oauth2/auth" //ds:def id=cloudflaretokenurl-t7wk2xqn owner=@khanakia stability=stable CloudflareTokenURL = "https://dash.cloudflare.com/oauth2/token" //ds:def id=cloudflareprofileurl-m2xq9wkt owner=@khanakia stability=stable CloudflareProfileURL = "https://api.cloudflare.com/client/v4/user" )
Cloudflare endpoints.
const ( //ds:def id=paypalauthurl-q7kn4wxt owner=@khanakia stability=stable PayPalAuthURL = "https://www.paypal.com/signin/authorize" //ds:def id=paypaltokenurl-x2wq8knt owner=@khanakia stability=stable PayPalTokenURL = "https://api-m.paypal.com/v1/oauth2/token" //ds:def id=paypalprofileurl-n9tk3xwq owner=@khanakia stability=stable PayPalProfileURL = "https://api-m.paypal.com/v1/identity/openidconnect/userinfo?schema=openid" //ds:def id=paypalsandboxauthurl-k4xq7wnt owner=@khanakia stability=stable PayPalSandboxAuthURL = "https://www.sandbox.paypal.com/signin/authorize" //ds:def id=paypalsandboxtokenurl-w6nk2xqt owner=@khanakia stability=stable PayPalSandboxTokenURL = "https://api-m.sandbox.paypal.com/v1/oauth2/token" //ds:def id=paypalsandboxprofileurl-t3wq9xkn owner=@khanakia stability=stable PayPalSandboxProfileURL = "https://api-m.sandbox.paypal.com/v1/identity/openidconnect/userinfo?schema=openid" )
PayPal endpoints, live and sandbox.
const ( //ds:def id=polarauthurl-x8wk3nqt owner=@khanakia stability=stable PolarAuthURL = "https://polar.sh/oauth2/authorize" //ds:def id=polartokenurl-k2tq7wxn owner=@khanakia stability=stable PolarTokenURL = "https://api.polar.sh/v1/oauth2/token" //ds:def id=polarprofileurl-n6xk9wqt owner=@khanakia stability=stable PolarProfileURL = "https://api.polar.sh/v1/oauth2/userinfo" )
Polar endpoints.
const ( //ds:def id=vercelauthurl-t9kq4wxn owner=@khanakia stability=stable VercelAuthURL = "https://vercel.com/oauth/authorize" //ds:def id=verceltokenurl-q3xn8wkt owner=@khanakia stability=stable VercelTokenURL = "https://api.vercel.com/login/oauth/token" //ds:def id=vercelprofileurl-m7wq2xkn owner=@khanakia stability=stable VercelProfileURL = "https://api.vercel.com/login/oauth/userinfo" )
Vercel endpoints.
const ( //ds:def id=patreonauthurl-w2kq9xnt owner=@khanakia stability=stable PatreonAuthURL = "https://www.patreon.com/oauth2/authorize" //ds:def id=patreontokenurl-x7nk4wqt owner=@khanakia stability=stable PatreonTokenURL = "https://www.patreon.com/api/oauth2/token" //ds:def id=patreonprofileurl-n3wq8xkt owner=@khanakia stability=stable PatreonProfileURL = "https://www.patreon.com/api/oauth2/v2/identity?fields%5Buser%5D=email,full_name,image_url,is_email_verified" )
Patreon endpoints.
const ( //ds:def id=gumroadauthurl-k9wn3xqt owner=@khanakia stability=stable GumroadAuthURL = "https://gumroad.com/oauth/authorize" //ds:def id=gumroadtokenurl-t4kq8wxn owner=@khanakia stability=stable GumroadTokenURL = "https://api.gumroad.com/oauth/token" //ds:def id=gumroadprofileurl-m8xq2wkt owner=@khanakia stability=stable GumroadProfileURL = "https://api.gumroad.com/v2/user" )
Gumroad endpoints.
const ( //ds:def id=hubspotauthurl-w7xk4nqt owner=@khanakia stability=stable HubSpotAuthURL = "https://app.hubspot.com/oauth/authorize" //ds:def id=hubspottokenurl-n2kq9wxt owner=@khanakia stability=stable HubSpotTokenURL = "https://api.hubapi.com/oauth/v1/token" // HubSpotTokenInfoURL is the endpoint that describes an access token, // the token itself appended; it is the only place HubSpot tells who the // token belongs to. //ds:def id=hubspottokeninfourl-q6wn3xkt owner=@khanakia stability=stable HubSpotTokenInfoURL = "https://api.hubapi.com/oauth/v1/access-tokens/" )
HubSpot endpoints.
const ( //ds:def id=yandexauthurl-k4nq7wxt owner=@khanakia stability=stable YandexAuthURL = "https://oauth.yandex.ru/authorize" //ds:def id=yandextokenurl-x9wk3nqt owner=@khanakia stability=stable YandexTokenURL = "https://oauth.yandex.ru/token" //ds:def id=yandexprofileurl-m3tq8wkn owner=@khanakia stability=stable YandexProfileURL = "https://login.yandex.ru/info?format=json" )
Yandex endpoints.
const ( ParamReauth = "reauth" ParamReauthTicket = "reauthTicket" ReauthFresh = "fresh" ReauthSecondFactor = "second_factor" )
The landing page's query after a re-authentication callback, beside what ReturnTo carried: ParamReauth is ReauthFresh or ReauthSecondFactor, and with the latter ParamReauthTicket is the ticket the second factor's re-authentication operation takes (twofactor.reauthenticate). The ticket is single use, bound to this session and valid for auth.PendingSessionTTL, so in the URL it is worth what an authorization code is. A refused callback lands on ErrorTo, or on ReturnTo when the start named none, with `?error=<code>`.
const ( //ds:def id=tiktokauthurl-r6kq2wxn owner=@khanakia stability=stable TikTokAuthURL = "https://www.tiktok.com/v2/auth/authorize/" //ds:def id=tiktoktokenurl-m3xw8kqt owner=@khanakia stability=stable TikTokTokenURL = "https://open.tiktokapis.com/v2/oauth/token/" //ds:def id=tiktokrevokeurl-h7nq4xwk owner=@khanakia stability=stable TikTokRevokeURL = "https://open.tiktokapis.com/v2/oauth/revoke/" // TikTokUserInfoURL asks for the user.info.basic fields only: open_id // (the subject), union_id, the large avatar and the display name. // TikTok has no email. //ds:def id=tiktokuserinfourl-t2wk9xqm owner=@khanakia stability=stable TikTokUserInfoURL = "https://open.tiktokapis.com/v2/user/info/?fields=open_id,union_id,avatar_large_url,display_name" )
TikTok endpoints (Login Kit for Web; developers.tiktok.com/doc/login-kit-web, oauth-user-access-token-management, tiktok-api-v2-get-user-info).
const ( //ds:def id=notionauthurl-k5wq8xnt owner=@khanakia stability=stable NotionAuthURL = "https://api.notion.com/v1/oauth/authorize" //ds:def id=notiontokenurl-x3nk7wqm owner=@khanakia stability=stable NotionTokenURL = "https://api.notion.com/v1/oauth/token" //ds:def id=notionmeurl-q8tw2kxn owner=@khanakia stability=stable NotionMeURL = "https://api.notion.com/v1/users/me" // NotionVersion is the Notion-Version header the profile call sends, // the version better-auth v1.7.7 pins. //ds:def id=notionversion-n4xq9wkt owner=@khanakia stability=stable NotionVersion = "2022-06-28" )
Notion endpoints (developers.notion.com/docs/authorization).
const ( //ds:def id=vkauthurl-c6nw2xkq owner=@khanakia stability=stable VKAuthURL = "https://id.vk.ru/authorize" //ds:def id=vktokenurl-f8kq3wxn owner=@khanakia stability=stable VKTokenURL = "https://id.vk.ru/oauth2/auth" //ds:def id=vkuserinfourl-j2xw7kqt owner=@khanakia stability=stable VKUserInfoURL = "https://id.vk.ru/oauth2/user_info" )
VK ID endpoints (id.vk.ru/about/business/go/docs, "API description").
const ( //ds:def id=wechatauthurl-v3kq8xwn owner=@khanakia stability=stable WeChatAuthURL = "https://open.weixin.qq.com/connect/qrconnect" //ds:def id=wechattokenurl-b7xw2kqn owner=@khanakia stability=stable WeChatTokenURL = "https://api.weixin.qq.com/sns/oauth2/access_token" //ds:def id=wechatrefreshurl-g4nq9xkt owner=@khanakia stability=stable WeChatRefreshURL = "https://api.weixin.qq.com/sns/oauth2/refresh_token" //ds:def id=wechatuserinfourl-z8kw3xqm owner=@khanakia stability=stable WeChatUserInfoURL = "https://api.weixin.qq.com/sns/userinfo" )
WeChat endpoints (developers.weixin.qq.com, Website App WeChat Login).
const ( WeChatLangCN = "cn" WeChatLangEN = "en" )
WeChat languages for the QR login page (better-auth's lang option).
const AnnotationAction = "oauth.action"
AnnotationAction is the Call annotation (auth.Call.Annotate) a callback, id-token or provider-initiated sign-in records once it resolved its user, so an After or Finally hook, plugin/audit's included, can tell what the flow did without reading its input or result. The value is an AccountAction.
const EndpointAccessToken auth.EndpointID = "oauth.access_token"
EndpointAccessToken is the ExposeAccessTokens route, POST /access-token.
const EndpointReauthenticate auth.EndpointID = "oauth.reauthenticate"
EndpointReauthenticate starts a re-authentication with a linked provider: POST /reauthenticate, signed in. It answers the provider URL for the page to navigate to; it never redirects, because a fetch would follow the redirect.
const EndpointReauthenticateIDToken auth.EndpointID = "oauth.reauthenticate-id-token"
EndpointReauthenticateIDToken re-authenticates the signed-in user with a provider ID token from a native SDK: POST /reauthenticate-id-token, signed in. The answer is auth.Reauthentication, as password.reauthenticate answers; when the second factor is due, its Ticket goes to the second factor's re-authentication operation with the code.
const LinkFlag = "true"
LinkFlag is the value of StartRequest.Link that starts a link flow.
const MaxPhotoDataURL = 4096
MaxPhotoDataURL is the longest data URL MicrosoftPhoto puts in Profile.Image: 4096 characters, the user image column on MySQL (VARCHAR(4096)), so the photo fits on every engine. A larger photo is left out and the profile keeps its image; a 48-pixel photo fits, a large one rarely does.
const MicrosoftPhotoURLFormat = "https://graph.microsoft.com/v1.0/me/photos/%dx%d/$value"
MicrosoftPhotoURLFormat is the Graph photo of the signed-in person at a size (%d twice).
const PluginName = "oauth"
PluginName is the plugin's permanent identity.
const PurposeIDTokenClaim auth.Purpose = "oauth.id_token_claim"
PurposeIDTokenClaim is the verification purpose of the default guard's claims. Its Identifier is the provider name, and the token's hash is IDTokenClaim.Key, so the stored row holds no part of the token.
const PurposeState auth.Purpose = "oauth.state"
PurposeState is the verification purpose of a flow's state. Its Identifier is the provider name; Meta is a JSON-encoded flowMeta.
const RefreshMargin = time.Minute
RefreshMargin is how long before expiry Tokens refreshes an access token, so a token handed to a caller does not expire on its way to the provider.
const SignUpFlag = "true"
SignUpFlag is the value of StartRequest.SignUp that asks for a sign-up.
const UIMethodKind = "oauth"
UIMethodKind is the SignInMethod.Kind of every provider's button, so a page styles them as one group.
const WarnIDTokenReplayAllowed = "oauth: AllowIDTokenReplay is set; a captured ID token can be used again until it expires"
WarnIDTokenReplayAllowed is logged at start-up under AllowIDTokenReplay.
const WarnLinkUnverifiedLocal = "" /* 139-byte string literal not displayed */
WarnLinkUnverifiedLocal is logged by Init under LinkUnverifiedLocalEmail.
const WarnStateCookieCheckOff = "" /* 138-byte string literal not displayed */
WarnStateCookieCheckOff is logged by Init under SkipStateCookieCheck.
Variables ¶
var ( // ErrAppleKey is a .p8 file that is not a PEM-encoded PKCS #8 P-256 key. ErrAppleKey = errors.New("oauth: the Apple key is a PEM-encoded PKCS #8 ECDSA P-256 private key") // ErrAppleSecret is a configuration error: AppleClientSecret with an // empty team id, client id or key id, or a nil key. ErrAppleSecret = errors.New("oauth: AppleClientSecret needs teamID, clientID, keyID and a key") )
Apple client-secret errors.
var ( // ErrAssertionKey is a key that is neither an ECDSA P-256 nor an RSA // private key, the two kinds a token endpoint accepts for a client // assertion (ES256, RS256). ErrAssertionKey = errors.New("oauth: ClientAssertion needs an ECDSA P-256 or RSA private key") // ErrAssertionConfig is a configuration error: an *OAuth2 with // PrivateKeyJWT and no ClientAssertion, or a ClientAssertion with another // TokenAuth, where it would never be sent. ErrAssertionConfig = errors.New("oauth: ClientAssertion goes with TokenAuth PrivateKeyJWT, and PrivateKeyJWT needs one") )
Client-assertion errors.
var ( // ErrIDTokenInvalid covers every ID token that must not be trusted: // malformed, an unlisted algorithm, an unknown key, a bad signature, // expired, another issuer or audience, or a nonce mismatch. The reason is // wrapped for the log; the client sees one answer. ErrIDTokenInvalid = auth.NewError("oauth.id_token_invalid", http.StatusUnauthorized, "The sign-in token was not accepted.") // ErrIDTokenUnsupported is an id-token sign-in for a provider that has no // IDTokenConfig (an *OAuth2 without one, an OIDC issuer whose discovery // document has no jwks_uri). ErrIDTokenUnsupported = auth.NewError("oauth.id_token_unsupported", http.StatusBadRequest, "This provider does not accept sign-in tokens.") // ErrNonceRequired is an ID token without a nonce claim while // RequireIDTokenNonce is set. ErrNonceRequired = auth.NewError("oauth.nonce_required", http.StatusUnauthorized, "The sign-in token carries no nonce.") // ErrJWKSFailed is a key-set fetch or parse failure; the user sees // ErrProviderFailed and the log the reason. ErrJWKSFailed = errors.New("oauth: provider key set could not be read") )
Errors of ID-token sign-in.
var ( TableAccount = sqlb.Table{Name: TableNameAccount, Alias: TableAliasAccount} AccountID = sqlb.Col[string](TableAccount, ColAccountID) AccountUserID = sqlb.Col[string](TableAccount, ColAccountUserID) AccountProvider = sqlb.Col[string](TableAccount, ColAccountProvider) AccountSubject = sqlb.Col[string](TableAccount, ColAccountSubject) AccountEmail = sqlb.Col[string](TableAccount, ColAccountEmail) AccountCreated = sqlb.Col[time.Time](TableAccount, ColAccountCreated) AccountAccess = sqlb.Col[string](TableAccount, ColAccountAccess) AccountRefresh = sqlb.Col[string](TableAccount, ColAccountRefresh) AccountExpires = sqlb.Col[*time.Time](TableAccount, ColAccountExpires) AccountIDToken = sqlb.Col[*string](TableAccount, ColAccountIDToken) AccountScope = sqlb.Col[string](TableAccount, ColAccountScope) AccountRefreshExpires = sqlb.Col[*time.Time](TableAccount, ColAccountRefreshExpires) AccountRefreshParams = sqlb.Col[*string](TableAccount, ColAccountRefreshParams) )
TableAccount and its typed columns.
var ( ErrStateInvalid = auth.NewError("oauth.state_invalid", http.StatusBadRequest, "This sign-in link has expired. Start again.") ErrProviderDenied = auth.NewError("oauth.denied", http.StatusBadRequest, "Sign-in was cancelled at the provider.") ErrProviderFailed = auth.NewError("oauth.provider_failed", http.StatusBadGateway, "The provider could not be reached. Try again.") ErrAccountExists = auth.NewError("oauth.account_exists", http.StatusConflict, "An account with this email already exists. Sign in, then link this provider in settings.") ErrAccountLinkedElsewhere = auth.NewError("oauth.linked_elsewhere", http.StatusConflict, "This provider account is linked to another user.") ErrNotLinked = auth.NewError("oauth.not_linked", http.StatusNotFound, "Not found.") ErrBadReturnTo = auth.NewError("oauth.bad_return_to", http.StatusBadRequest, "The return address must be a path on this site or a trusted origin.") ErrRefreshFailed = auth.NewError("oauth.refresh_failed", http.StatusBadGateway, "The provider did not renew access. Link the account again.") ErrNoTokens = auth.NewError("oauth.no_tokens", http.StatusNotFound, "No provider tokens are stored for this account.") ErrBadTokenKey = errors.New("oauth: StoreTokens needs a 32-byte key, or auth.WithSecret to derive one") // ErrSignUpDisabled is a callback that would create a user while // NoSignUp is set. ErrSignUpDisabled = auth.NewError("oauth.sign_up_disabled", http.StatusForbidden, "Sign-up with this provider is not open. Sign in to an existing account first.") // ErrEmailUnverified is a provider profile whose email is not verified // while RequireVerifiedEmail is set. ErrEmailUnverified = auth.NewError("oauth.email_unverified", http.StatusForbidden, "The provider has not verified your email address.") // ErrEmailNotFound is a provider profile with no email at all while // RequireEmail or VerifyEmailBeforeSignIn is set: better-auth's // email_not_found. RequireVerifiedEmail answers it ErrEmailUnverified. ErrEmailNotFound = auth.NewError("oauth.email_not_found", http.StatusForbidden, "The provider did not share an email address. Allow access to your email and try again.") // ErrEmailNotVerified is the code a sign-in held by // VerifyEmailBeforeSignIn lands with (`?error=oauth.email_not_verified`): // the user exists, and no session was issued until the address is // verified. better-auth's email_not_verified. It is a code in a redirect // and a flag in the result, never a failed call, because the user the // callback created must not be rolled back. ErrEmailNotVerified = auth.NewError("oauth.email_not_verified", http.StatusForbidden, "Verify your email address, then sign in again.") // ErrBadParam is a StartRequest.Params that does not parse, is too long, // or names a parameter the provider does not list in OptionalParams. ErrBadParam = auth.NewError("oauth.bad_param", http.StatusBadRequest, "A requested sign-in parameter is not available for this provider.") // ErrBadScope is a StartRequest.Scopes entry the provider does not list // in OptionalScopes. ErrBadScope = auth.NewError("oauth.bad_scope", http.StatusBadRequest, "A requested scope is not available for this provider.") // ErrRequestParams is a start with prompt, loginHint or scopes for a // Provider that is not a ParamsProvider, so the parameters would be // dropped silently. ErrRequestParams = auth.NewError("oauth.params_unsupported", http.StatusBadRequest, "This provider does not take sign-in options.") // ErrEmailMismatch is a link flow, under RequireSameEmailOnLink, whose // provider address belongs to nobody or to another user. ErrEmailMismatch = auth.NewError("oauth.email_mismatch", http.StatusConflict, "The provider account uses an address that is not on this account.") // ErrProviderIncomplete is a configuration error: an *OAuth2 without an // AuthURL, a TokenURL (unless GetToken replaces the exchange), a // ClientID or FetchProfile (unless the profile comes from the ID token), // or an unknown TokenAuth, or a PublicClient with a secret or a // ClientSecretFunc. ErrProviderIncomplete = errors.New("oauth: a provider needs AuthURL, TokenURL (or GetToken), ClientID, FetchProfile (or IDToken.ProfileFromToken) and a known TokenAuth (no secret for a PublicClient)") // ErrIDTokenConfig is a configuration error: an IDTokenConfig with a // JWKSURL but no Issuers, or ProfileFromToken without a JWKSURL. ErrIDTokenConfig = errors.New("oauth: IDToken needs JWKSURL and Issuers (ProfileFromToken needs both)") // ErrLogoutUnsupported is a logout for a provider with no end-session // endpoint: an *OAuth2 without EndSessionURL, or an issuer whose // discovery document publishes none. ErrLogoutUnsupported = auth.NewError("oauth.logout_unsupported", http.StatusNotFound, "This provider has no sign-out endpoint.") // ErrDataTooLong is a StartRequest.Data longer than maxFlowDataLength. ErrDataTooLong = auth.NewError("oauth.data_too_long", http.StatusBadRequest, "The data to carry through sign-in is too long.") // ErrSignUpNotRequested is a callback that would create a user under // ExplicitSignUp while the request did not ask for a sign-up. ErrSignUpNotRequested = auth.NewError("oauth.sign_up_not_requested", http.StatusForbidden, "No account is linked to this provider account. Use sign-up to create one.") // ErrProfileRefused is a ValidateProfile hook that refused the profile // with an error that is not an *auth.Error; the hook's reason is logged. ErrProfileRefused = auth.NewError("oauth.profile_refused", http.StatusForbidden, "This account cannot sign in here.") // ErrCannotRefresh is Refresh for an account with no refresh token, or // at a provider that is not a Refresher. ErrCannotRefresh = auth.NewError("oauth.cannot_refresh", http.StatusNotFound, "This account's tokens cannot be refreshed.") // ErrProviderUnknown is an ImportAccount for a provider the plugin was // not built with, where nothing could ever sign in with the row, or a // Start or Callback for a provider that is not in use. ErrProviderUnknown = errors.New("oauth: provider is not configured") // ErrImportSubject is an ImportAccount with an empty user id or provider // account id. ErrImportSubject = errors.New("oauth: ImportAccount needs a user id and a provider account id") ErrNoProviders = errors.New("oauth: no providers") ErrNoCallbackURL = errors.New("oauth: callback URLs need auth.WithBaseURL or oauth.CallbackURL") )
Errors.
var AccountActionValues = []AccountAction{AccountSignIn, AccountSignUp, AccountLinked, AccountLinkedByEmail, AccountReauthenticated}
AccountActionValues is every AccountAction, for documentation and tests.
var CallbackParamValues = []CallbackParam{CallbackParamState, CallbackParamDeviceID}
CallbackParamValues is every CallbackParam, for validation.
var ErrAllProvidersBroken = errors.New("oauth: every provider is broken")
ErrAllProvidersBroken is auth.New under SkipBrokenProviders when every provider was left out: there is nothing left to keep working, so the failure is loud. It wraps each provider's reason.
var ErrCallbackParam = errors.New("oauth: unknown callback parameter")
ErrCallbackParam is a configuration error: a CallbackParams entry that is not one of CallbackParamValues.
var ErrExchange = errors.New("oauth: provider exchange failed")
ErrExchange is a failed code exchange or profile read. Its text is for the log; the client sees ErrProviderFailed.
var ErrExposeNeedsStoreTokens = errors.New("oauth: ExposeAccessTokens needs StoreTokens")
ErrExposeNeedsStoreTokens is ExposeAccessTokens without StoreTokens: the route would only ever answer ErrNoTokens.
var ErrFallbackIncomplete = errors.New("oauth: an OIDC Fallback needs AuthURL, TokenURL and UserInfoURL")
ErrFallbackIncomplete is a configuration error: an OIDCProvider.Fallback with some endpoint set but no AuthURL, TokenURL or UserInfoURL, which could not sign anyone in.
var ErrHostedDomain = fmt.Errorf("%w: the account is not in the required Google Workspace domain", ErrExchange)
ErrHostedDomain is a Google userinfo whose hd claim is not the Workspace domain the app requires. It wraps ErrExchange, so the user sees ErrProviderFailed and the log says which domain answered.
var ErrIDTokenReplayConfig = errors.New("oauth: ID-token replay protection")
ErrIDTokenReplayConfig refuses a replay configuration that cannot work: a guard given together with AllowIDTokenReplay, or the default guard over a verification store that does not refuse duplicates.
var ErrIDTokenReplayed = auth.NewError("oauth.id_token_replayed", http.StatusUnauthorized, "This sign-in token was already used. Sign in with the provider again.")
ErrIDTokenReplayed refuses an ID token that was already accepted once, by a native sign-in or an ID-token re-authentication. It says only that this token is spent, which its holder can learn by using it, so it reveals no account. The app asks the provider's SDK for a new token and tries again.
var ErrLogoutProvider = auth.NewError("oauth.logout_provider", http.StatusBadRequest, "Name the provider to sign out of.")
ErrLogoutProvider is a logout request that names no provider on the general route, or another provider than its own route's.
var ErrPhotoSize = errors.New("oauth: MicrosoftPhoto size must be one of MicrosoftPhotoSizes")
ErrPhotoSize is MicrosoftPhoto given a size Graph does not serve.
var ErrProfileUnsupported = auth.NewError("oauth.profile_unsupported", http.StatusNotFound, "This provider does not offer a profile to read.")
ErrProfileUnsupported is a profile read on a provider that has no profile call: its profile comes only from the ID token of the exchange.
var ErrProviderInitiatedConfig = errors.New("oauth: AllowProviderInitiated needs a configured provider that verifies ID tokens (an OIDC provider, or OAuth2 with IDToken)")
ErrProviderInitiatedConfig is a configuration error: AllowProviderInitiated or RestartProviderInitiated names a provider that is not configured, both name the same one, or AllowProviderInitiated names one that cannot verify an ID token (an *OAuth2 without IDToken, or a custom Provider that is not an InitiatedExchanger). Without a verified ID token nothing would vouch for a code no request of this app asked for.
var ErrPublicClientPKCE = errors.New("oauth: NoPKCE needs a client secret; a public client has nothing else tying the code to it")
ErrPublicClientPKCE is a configuration error: NoPKCE on a provider with TokenAuth PublicClient. With no secret and no PKCE, anyone holding the code could redeem it.
var ErrReauthNotLinked = auth.NewError("oauth.reauth_not_linked", http.StatusForbidden, "Continue with the provider account that is linked to this account.")
ErrReauthNotLinked refuses a re-authentication with a provider the user has no account linked at, or whose callback brought back a provider account that is not linked to the signed-in user (another person's, or one never linked). One code for every case, so the answer does not tell which accounts exist.
var ErrRedirectRefused = errors.New("oauth: provider answered with a redirect; server-side provider calls do not follow redirects")
ErrRedirectRefused is the error a provider call fails with when the provider answered with an HTTP redirect: the token, profile, discovery, key-set, refresh and revocation endpoints are called from the server, and following a redirect there would let whoever controls one of those URLs (a misconfigured issuer, a compromised discovery document) bounce the server to an internal address (SSRF). better-auth refuses them the same way. The flow fails with ErrProviderFailed and this error is logged; configure the final endpoint URL.
var ErrServerContextTooLong = errors.New("oauth: WithServerContext values exceed 1024 characters as JSON")
ErrServerContextTooLong is a server context whose JSON exceeds maxServerContextLength: a programming error of the app's function, so it is a plain error (a 500), not a client answer.
var ErrStateCookieKey = errors.New("oauth: StateInCookie needs auth.WithSecret, and cannot be combined with SkipStateCookieCheck")
ErrStateCookieKey is StateInCookie without auth.WithSecret to derive its key from, or combined with SkipStateCookieCheck.
var MicrosoftPhotoSizes = []MicrosoftPhotoSize{MicrosoftPhoto48, MicrosoftPhoto64, MicrosoftPhoto96, MicrosoftPhoto120, MicrosoftPhoto240, MicrosoftPhoto360, MicrosoftPhoto432, MicrosoftPhoto504, MicrosoftPhoto648}
MicrosoftPhotoSizes is every MicrosoftPhotoSize.
var Presets = []string{
"google", "github", "apple", "microsoft", "discord", "facebook", "slack", "linkedin", "gitlab", "twitch", "line",
"huggingface", "roblox", "salesforce", "auth0", "okta", "keycloak", "zitadel", "cognito", "spotify", "dropbox",
"x", "reddit", "atlassian", "zoom", "figma", "kakao", "naver", "kick", "linear", "cloudflare", "paypal", "polar",
"vercel", "patreon", "gumroad", "hubspot", "yandex", "paybin", "railway", "tiktok", "notion", "vk", "wechat",
"dub", "instagram", "snapchat", "zoho",
}
Presets lists every provider constructor's route segment, for documentation and for a test that keeps the README's table complete.
var ProfileActionValues = []ProfileAction{ActionSignIn, ActionSignUp, ActionLink}
ProfileActionValues is every ProfileAction, for documentation and tests.
var TokenAuthValues = []TokenAuth{ClientSecretPost, ClientSecretBasic, PublicClient, PrivateKeyJWT}
TokenAuthValues is every TokenAuth, for validation and documentation.
var ZohoRegionValues = []ZohoRegion{ZohoUS, ZohoEU, ZohoIndia, ZohoAustralia, ZohoJapan, ZohoUK, ZohoCanada, ZohoSaudi, ZohoChina}
ZohoRegionValues lists every ZohoRegion, for a configuration screen and for the test that keeps the set closed.
Functions ¶
func AppleClientSecret ¶
func AppleClientSecret(teamID, clientID, keyID string, key *ecdsa.PrivateKey) (func(ctx context.Context) (string, error), error)
AppleClientSecret returns a ClientSecretFunc that signs Apple's client-secret assertion: an ES256 JWT with the team id as issuer, the client id as subject and Apple as audience, valid appleSecretLifetime and renewed before it expires. teamID and keyID come from the Apple developer account; key is ParseAppleKey of the .p8 file.
func ApplePostedUser ¶
ApplePostedUser reads Apple's user field ({"name":{"firstName","lastName"}, "email"}) into the profile: the name joined with a space when the profile has none, and the email only when the ID token carried none. The field comes from the browser, not from Apple's signed token, so it never overrides what the token said and never marks an address verified. A field that is not that JSON is ignored.
func AppleScopes ¶
func AppleScopes() []string
AppleScopes are the scopes Sign in with Apple offers: the email (in the ID token) and the name (in the user form field, on first consent only). A function, so no caller can change another's list.
func AtlassianProfile ¶
func AtlassianProfile(u string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
AtlassianProfile reads Atlassian's /me answer from u.
func CallbackEndpoint ¶
func CallbackEndpoint(provider string) auth.EndpointID
CallbackEndpoint names a provider's callback operation: "oauth.<provider>-callback".
func ClientAssertion ¶
func ClientAssertion(clientID, kid string, key crypto.Signer) (func(ctx context.Context, tokenURL string) (string, error), error)
ClientAssertion returns an OAuth2.ClientAssertion that signs the RFC 7523 client assertion for TokenAuth PrivateKeyJWT: a JWT with the client id as issuer and subject, the token endpoint as audience, a fresh jti and a lifetime of assertionLifetime, signed ES256 with a P-256 key or RS256 with an RSA key. kid is the key id registered at the provider, written in the header so the provider picks the right public key. Entra ID (workload identity federation, certificate credentials) and any issuer that refuses shared secrets take this form.
func CloudflareProfile ¶
func CloudflareProfile(u string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
CloudflareProfile reads Cloudflare's /client/v4/user answer from u.
func DiscordProfile ¶
func DiscordProfile(u string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
DiscordProfile reads Discord's /users/@me answer from u.
func DropboxProfile ¶
func DropboxProfile(u string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
DropboxProfile posts to Dropbox's get_current_account at u.
func DubProfile ¶
func DubProfile(u string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
DubProfile reads Dub's /oauth/userinfo answer from u: id, name, image.
func FacebookProfile ¶
func FacebookProfile(u string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
FacebookProfile reads Facebook's /me answer from u.
func FigmaProfile ¶
func FigmaProfile(u string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
FigmaProfile reads Figma's /v1/me answer from u.
func FormPostEndpoint ¶
func FormPostEndpoint(provider string) auth.EndpointID
FormPostEndpoint names a provider's form-post relay operation: "oauth.<provider>-form-post", served as POST at the callback route. It exists only for a FormPoster.
func GitHubProfile ¶
func GitHubProfile(base string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
GitHubProfile reads the GitHub user and primary email from the API at base.
func GoogleProfile ¶
func GoogleProfile(u string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
GoogleProfile reads an OpenID Connect userinfo response from u.
func GoogleProfileFor ¶
func GoogleProfileFor(u, hostedDomain string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
GoogleProfileFor is GoogleProfile restricted to one Google Workspace domain: a userinfo whose hd claim is not hostedDomain is refused. The hd parameter on the authorization URL is only a hint to the account picker; this check on the returned claim is the enforcement. An empty hostedDomain accepts every account.
func GumroadProfile ¶
func GumroadProfile(u string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
GumroadProfile reads Gumroad's /v2/user answer from u.
func HubSpotProfile ¶
func HubSpotProfile(base string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
HubSpotProfile reads HubSpot's access-token description at base + token.
func IDTokenEndpoint ¶
func IDTokenEndpoint(provider string) auth.EndpointID
IDTokenEndpoint names a provider's id-token operation: "oauth.<provider>-id-token". It exists only for an IDTokenVerifier.
func InstagramProfile ¶
func InstagramProfile(u string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
InstagramProfile reads the Graph API /me answer from u: the app-scoped id as subject (better-auth-instagram's), the name or else the username, and the profile picture.
func KakaoProfile ¶
func KakaoProfile(u string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
KakaoProfile reads Kakao's /v2/user/me answer from u.
func KickProfile ¶
func KickProfile(u string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
KickProfile reads Kick's /public/v1/users answer from u.
func LinearProfile ¶
func LinearProfile(u string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
LinearProfile posts the viewer query to Linear's GraphQL endpoint at u.
func LogoutEndpoint ¶
func LogoutEndpoint(provider string) auth.EndpointID
LogoutEndpoint names a provider's logout operation: "oauth.<provider>-logout". It exists only for a Logouter.
func MicrosoftPhoto ¶
func MicrosoftPhoto(fetch func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error), size MicrosoftPhotoSize) (func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error), error)
MicrosoftPhoto wraps a Microsoft FetchProfile so the profile's image is the person's Graph photo at size, as a data URL (better-auth's profilePhotoSize; leaving it unwrapped is disableProfilePhoto, our default). A photo that is missing (404), unreadable, or longer than MaxPhotoDataURL as a data URL leaves the image as the profile had it: a sign-in never fails over a photo. size must be one of MicrosoftPhotoSizes.
m := oauth.Microsoft(tenant, id, secret) m.FetchProfile, err = oauth.MicrosoftPhoto(m.FetchProfile, oauth.MicrosoftPhoto48)
func NaverProfile ¶
func NaverProfile(u string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
NaverProfile reads Naver's /v1/nid/me answer from u.
func NotionProfile ¶
func NotionProfile(u string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
NotionProfile reads Notion's /v1/users/me answer from u: a bot whose owner.user is the person who authorized it.
func PKCEChallenge ¶
PKCEChallenge is the RFC 7636 S256 code challenge of verifier: the unpadded base64url SHA-256. Exported for a flow that runs a Provider without this plugin (authgate): *OAuth2 with IDToken.ProfileFromToken also sends this value as the OpenID Connect nonce and recomputes it in Exchange, so a caller that builds the authorization URL must derive the challenge by exactly this rule or the ID token's nonce check fails.
func ParseAppleKey ¶
func ParseAppleKey(p8 []byte) (*ecdsa.PrivateKey, error)
ParseAppleKey reads the .p8 file Apple issues for Sign in with Apple.
func PatreonProfile ¶
func PatreonProfile(u string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
PatreonProfile reads Patreon's identity answer from u.
func RedditProfile ¶
func RedditProfile(u, userAgent string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
RedditProfile reads Reddit's /api/v1/me answer from u, sending userAgent.
func SnapchatProfile ¶
func SnapchatProfile(u string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
SnapchatProfile reads Snap Kit's /v1/me at u with the GraphQL query for externalId, displayName and the Bitmoji avatar. A GraphQL error, or an answer with no externalId (the user.external_id scope not granted), is a failed read.
func SpotifyProfile ¶
func SpotifyProfile(u string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
SpotifyProfile reads Spotify's /v1/me answer from u.
func StartEndpoint ¶
func StartEndpoint(provider string) auth.EndpointID
StartEndpoint names a provider's start operation: "oauth.<provider>-start".
func TikTokProfile ¶
func TikTokProfile(u string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
TikTokProfile reads TikTok's /v2/user/info/ answer from u: data.user with open_id, display_name and avatar_large_url. An answer whose error.code is not "ok" is a failed read.
func UserInfoProfile ¶
func UserInfoProfile(u, subjectClaim string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
UserInfoProfile reads an OpenID Connect userinfo answer from u and takes subjectClaim as the subject instead of sub, for an issuer whose stable identifier is another claim (Entra ID's oid). An empty subjectClaim is sub. The claim must be a string or a number; anything else is a profile with no subject, which the exchange refuses.
func VKProfile ¶
func VKProfile(u, clientID string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
VKProfile reads VK ID's user_info answer from u, posting the access token and clientID as a form, which is how VK ID takes them.
func ValidateProvider ¶
ValidateProvider is the configuration check New's Validate runs on every provider: a route-safe name, and for the built-in shapes the fields a sign-in cannot work without (endpoints, client id, a profile source, a coherent token-endpoint authentication, a usable ID-token configuration). Exported so a flow that runs a Provider without this plugin (authgate) refuses the same broken configurations at startup. A Provider of another type passes on its name alone: its own constructor owns its checks.
func WeChatProfile ¶
func WeChatProfile(u string) func(ctx context.Context, hc *http.Client, ts TokenSet) (Profile, error)
WeChatProfile reads WeChat's /sns/userinfo from u for the openid the token answer carried (TokenSet.Raw).
func WithCallbackValues ¶
func WithCallbackValues(ctx context.Context, v CallbackValues) context.Context
WithCallbackValues returns ctx carrying v, for the exchange the callback runs; a test or a flow outside this plugin (authgate) sets it the same way.
func XProfile ¶
func XProfile(u string) func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
XProfile reads X's /2/users/me answer from u.
Types ¶
type AccessTokenRequest ¶
type AccessTokenRequest struct {
Provider string `json:"provider"`
}
AccessTokenRequest names the provider whose token the signed-in user wants.
type AccessTokenResult ¶
type AccessTokenResult struct {
// AccessToken is revealed on purpose: delivering it is this route's
// job (ExposeAccessTokens).
AccessToken string `json:"accessToken"`
// ExpiresAt is when AccessToken expires; nil when the provider did not
// say.
ExpiresAt *time.Time `json:"accessTokenExpiresAt"`
// Scopes are the scopes the provider said it granted.
Scopes []string `json:"scopes"`
// IDToken is the OpenID Connect ID token of the last exchange, "" when
// the provider issued none.
IDToken string `json:"idToken,omitempty"`
}
AccessTokenResult is the signed-in user's provider token, without the refresh token, which stays on the server.
type Account ¶
type Account struct {
ID string `json:"id"`
UserID string `json:"-"`
Provider string `json:"provider"`
Subject string `json:"-"`
Email string `json:"email"`
CreatedAt time.Time `json:"createdAt"`
// Scopes are the scopes the provider said it granted at the last
// exchange or refresh (better-auth's listAccounts scopes), so a page can
// tell whether to ask for more; empty when tokens are not stored
// (StoreTokens) or the provider did not say. Scope names are not
// secrets; the tokens themselves never appear here.
Scopes []string `json:"scopes"`
// contains filtered or unexported fields
}
Account is one linked provider account. UserID and Subject are hidden from JSON: a client never needs the provider's id for the person.
type AccountAction ¶
type AccountAction string
AccountAction is the value of AnnotationAction.
const ( // AccountSignIn: a linked provider account signed its user in. AccountSignIn AccountAction = "sign_in" // AccountSignUp: a user was created for the provider account. AccountSignUp AccountAction = "sign_up" // AccountLinked: a signed-in user linked the provider account (a link // flow, or an id-token call from a session). AccountLinked AccountAction = "link" // AccountLinkedByEmail: the provider account was linked to an existing // user because both sides' verified addresses matched. AccountLinkedByEmail AccountAction = "link_email" // AccountReauthenticated: a signed-in user confirmed who they are with a // linked provider account (EndpointReauthenticate, or // EndpointReauthenticateIDToken); nothing was signed in, created or // linked. AccountReauthenticated AccountAction = "reauthenticate" )
What an OAuth flow did. Linking is split by how it happened, because an audit log reads them differently: a link the signed-in user asked for, and a link the plugin made because the verified addresses matched.
type AccountsResult ¶
type AccountsResult struct {
Accounts []Account `json:"accounts"`
}
AccountsResult is the output of accounts: the signed-in user's linked providers, oldest first.
type CallbackParam ¶
type CallbackParam string
CallbackParam names a callback query parameter a provider's token request carries as well (OAuth2.CallbackParams). A closed set: the plugin forwards only values it decoded and checked itself.
const ( // CallbackParamState forwards the callback's state, which VK ID's token // request requires. The state is spent by then; sending it back to the // provider that issued it reveals nothing. CallbackParamState CallbackParam = "state" // CallbackParamDeviceID forwards VK ID's device_id. CallbackParamDeviceID CallbackParam = "device_id" )
type CallbackRequest ¶
type CallbackRequest struct {
Code string `json:"code,omitempty"`
State string `json:"state"`
Error string `json:"error,omitempty"`
// Providers add fields such as error_description and scope; they are
// accepted and ignored.
ErrorDescription string `json:"error_description,omitempty"`
Scope string `json:"scope,omitempty"`
AuthUser string `json:"authuser,omitempty"`
Prompt string `json:"prompt,omitempty"`
HD string `json:"hd,omitempty"`
// Iss is the RFC 9207 issuer identifier, checked against the provider's
// AuthorizationIssuer when it has one.
Iss string `json:"iss,omitempty"`
// User is the user field of a form-post callback (Apple's first-consent
// name and email), relayed from the POST and read by the provider's
// PostedProfile after the exchange; ignored by any other provider.
User string `json:"user,omitempty"`
// DeviceID is VK ID's device_id, which its token request requires; a
// provider forwards it only when its CallbackParams name it.
DeviceID string `json:"device_id,omitempty"`
}
CallbackRequest is what the provider sends back.
type CallbackResult ¶
type CallbackResult struct {
User auth.UserView `json:"user"`
// Created is true when the callback created the user.
Created bool `json:"created"`
// Linked is true when the callback linked the provider to the signed-in
// user.
Linked bool `json:"linked"`
RedirectTo string `json:"redirectTo"`
// SecondFactorRequired is true when the session is pending a second
// factor; the app page at RedirectTo asks for it.
SecondFactorRequired bool `json:"secondFactorRequired"`
// EmailVerificationRequired is true when VerifyEmailBeforeSignIn held
// the sign-in: the user exists, no session was issued, and RedirectTo
// carries `?error=oauth.email_not_verified`.
EmailVerificationRequired bool `json:"emailVerificationRequired,omitempty"`
Token auth.Token `json:"-"`
// Data is what the start request carried in StartRequest.Data; empty
// for an id-token sign-in.
Data string `json:"data"`
// ServerContext is what the WithServerContext functions returned at
// the start (better-auth's serverContext): server-set values, never
// sent to the client (json "-"), read by an After hook on the callback.
ServerContext map[string]string `json:"-"`
// Reauthentication is the outcome of a re-authentication callback
// (EndpointReauthenticate), nil for every other flow: the session is
// fresh, or the second factor is due with a ticket. No session is
// issued and Token stays empty; RedirectTo carries ParamReauth.
Reauthentication *auth.Reauthentication `json:"reauthentication,omitempty"`
}
CallbackResult is the outcome of a callback.
type CallbackValues ¶
CallbackValues are the callback's values a token request may need beyond the code: what VK ID's exchange requires. The callback puts them in the exchange's context; a Provider of the app's own reads them with CallbackValuesOf (better-auth hands deviceId to validateAuthorizationCode).
func CallbackValuesOf ¶
func CallbackValuesOf(ctx context.Context) CallbackValues
CallbackValuesOf returns the callback values in ctx, zero outside a callback's exchange.
type Discoverer ¶
Discoverer is a provider that must look up its endpoints before its first flow. The plugin calls Discover at every start; an implementation caches.
type Endpoints ¶
type Endpoints struct {
AuthURL string
TokenURL string
UserInfoURL string
// JWKSURL is the key set, for ID tokens; empty accepts none while the
// fallback is in use.
JWKSURL string
EndSessionURL string
RevokeURL string
// IssParameterSupported is the discovery flag of RFC 9207: set it when
// the issuer sends iss on every authorization response, so the callback
// keeps checking it while the fallback is in use.
IssParameterSupported bool
}
Endpoints are an OpenID Connect issuer's endpoints written down by hand, for OIDCProvider.Fallback. AuthURL, TokenURL and UserInfoURL are required once any field is set (ErrFallbackIncomplete); the others are optional, as in a discovery document.
type FormPostRequest ¶
type FormPostRequest struct {
Code string `json:"code"`
State string `json:"state"`
Error string `json:"error"`
// User is Apple's first-consent user field (name and email as JSON).
User string `json:"user"`
}
FormPostRequest is what a response_mode=form_post provider posts to the callback route: the same fields as CallbackRequest, as a form.
type FormPostResult ¶
type FormPostResult struct {
RedirectTo string `json:"redirectTo"`
}
FormPostResult is the GET callback URL the relay redirects to.
type FormPoster ¶
FormPoster is a Provider that answers by response_mode=form_post. For each one the plugin also serves POST at the callback route: a relay that turns the cross-site form into the GET callback the flow cookie travels with. *OAuth2 implements it through FormPost.
type IDTokenClaim ¶
type IDTokenClaim struct {
// Key identifies the token's issuance: the hex SHA-256 of its signed
// part (the JWS header and payload, without the signature), or of the
// whole token when it is not a compact JWS. Not the raw token, because
// an ES256 signature has a second valid form (s and n-s), so one
// issuance has two spellings; and not issuer, subject, audience and jti,
// because jti is optional (Apple's tokens carry none) and two tokens
// issued in one second would share the rest. Every claim the provider
// signed is in the key, so two different issuances never share it.
Key string
// Provider is the provider that verified the token.
Provider string
// Until is when the claim may be forgotten: the token's exp plus the
// verifier's clock leeway, after which the token is refused as expired
// anyway. A verifier whose profile carries no exp claim gets
// idTokenClaimFallback from now.
Until time.Time
}
IDTokenClaim is one accepted ID token, as the guard is asked to claim it.
type IDTokenConfig ¶
type IDTokenConfig struct {
// JWKSURL is where the provider publishes its signing keys. Required.
JWKSURL string
// Issuers are the iss values accepted; Google uses two spellings. At
// least one is required.
Issuers []string
// Audiences are accepted aud values besides ClientID: an iOS bundle id,
// an Android client id. The web ClientID is always accepted.
Audiences []string
// ProfileFromToken makes Exchange read the profile from the id_token in
// the token response instead of calling FetchProfile, for a provider that
// has no userinfo endpoint. The token's nonce is the flow's PKCE
// challenge, which AuthCodeURL sends as the nonce parameter.
ProfileFromToken bool
// RequireNonce refuses an ID token that carries no nonce claim
// (ErrNonceRequired), for an app whose clients always send one: a token
// replayed from another app's sign-in then has no way in.
RequireNonce bool
// HostedDomain refuses a token whose hd claim is not this Google
// Workspace domain (ErrIDTokenInvalid), the native counterpart of
// GoogleProfileFor; empty accepts every account. Only Google sets hd.
HostedDomain string
}
IDTokenConfig makes a provider accept ID tokens: tokens a native SDK (Google One Tap, Sign in with Apple, the Android and iOS libraries) hands the app, verified here against the provider's published keys, and tokens the provider returns from its token endpoint, for a provider that has no userinfo endpoint (Apple). The zero value accepts none.
func GoogleIDTokens ¶
func GoogleIDTokens(audiences ...string) IDTokenConfig
GoogleIDTokens makes oauth.Google accept ID tokens on its id-token route: Google One Tap in the browser, and the Android and iOS sign-in libraries. audiences are the app's other client ids (the iOS and Android ones); the web ClientID is always accepted. Set it on the provider:
g := oauth.Google(id, secret)
g.IDToken = oauth.GoogleIDTokens("ios-client-id")
hostedDomain, when given through GoogleIDTokensFor, refuses tokens whose hd claim is another Workspace domain, the native counterpart of GoogleProfileFor.
func GoogleIDTokensFor ¶
func GoogleIDTokensFor(hostedDomain string, audiences ...string) IDTokenConfig
GoogleIDTokensFor is GoogleIDTokens restricted to one Google Workspace domain: a token whose hd claim is not hostedDomain is ErrIDTokenInvalid. An empty hostedDomain accepts every account.
func (IDTokenConfig) Enabled ¶
func (c IDTokenConfig) Enabled() bool
Enabled reports whether the provider accepts ID tokens.
type IDTokenReplayGuard ¶
type IDTokenReplayGuard interface {
Claim(ctx context.Context, claim IDTokenClaim) error
}
IDTokenReplayGuard decides whether an ID token may be accepted: Claim records claim.Key as used until claim.Until and returns nil the first time, and ErrIDTokenReplayed (or an error wrapping it) for a key already claimed and not yet past its Until. Of concurrent claims of one key exactly one may succeed. Any other error is a failure of the guard, and the operation fails with it rather than accept an unchecked token.
type IDTokenReplayGuardFunc ¶
type IDTokenReplayGuardFunc func(ctx context.Context, claim IDTokenClaim) error
IDTokenReplayGuardFunc adapts a function to IDTokenReplayGuard.
func (IDTokenReplayGuardFunc) Claim ¶
func (f IDTokenReplayGuardFunc) Claim(ctx context.Context, claim IDTokenClaim) error
Claim calls f.
type IDTokenRequest ¶
type IDTokenRequest struct {
// IDToken is the provider's ID token, a compact JWS.
IDToken string `json:"idToken"`
// Nonce is the value the app sent to the provider, when it sent one;
// the token's nonce claim must be it or its SHA-256 hex.
Nonce string `json:"nonce,omitempty"`
// SignUp says the person meant to sign up, which ExplicitSignUp
// requires before a user is created.
SignUp bool `json:"signUp,omitempty"`
// AccessToken is the access token the native SDK received with the ID
// token, better-auth's idToken.accessToken. It is taken only when the ID
// token binds it: an at_hash claim (OpenID Connect Core 3.1.3.6) equal
// to the hash of this value, so a client cannot attach a stolen or
// unrelated token to a verified identity. A token without at_hash, or
// with another hash, is ErrIDTokenInvalid. Once taken, it is the
// account's stored access token under StoreTokens, like a callback's.
AccessToken string `json:"accessToken,omitempty"`
}
IDTokenRequest is the input of an id-token operation: a token the app received from the provider's native SDK.
type IDTokenVerifier ¶
type IDTokenVerifier interface {
Provider
// VerifyIDToken checks the token's signature, issuer, audience and
// lifetime, and its nonce against nonce when the token carries one. A
// token that must not be trusted is ErrIDTokenInvalid; a provider not
// configured for ID tokens is ErrIDTokenUnsupported.
VerifyIDToken(ctx context.Context, hc *http.Client, token, nonce string) (Profile, error)
}
IDTokenVerifier is a Provider that can verify a provider-issued ID token and turn its claims into a Profile. *OAuth2 and *OIDCProvider implement it; the id-token operation exists only for providers that do.
type ImportAccount ¶
type ImportAccount struct {
// UserID is the user here the account belongs to; it must exist.
UserID string
// Provider is the configured provider's name (ErrProviderUnknown otherwise).
Provider string
// ProviderAccountID is the provider's stable id for the person, stored as
// the subject; "Sign in with <provider>" then lands on UserID.
ProviderAccountID string
// Email is the address the provider reported there, kept as information.
Email string
}
ImportAccount is one linked account from another system, for ImportAccount.
type InitiatedExchanger ¶
type InitiatedExchanger interface {
Provider
// ExchangeInitiated trades code for the person's profile without a
// verifier. The token response must carry an ID token whose signature,
// issuer, audience and lifetime verify and which carries no nonce, and
// the profile's subject must be the token's.
ExchangeInitiated(ctx context.Context, hc *http.Client, code, redirectURI string) (Profile, error)
}
InitiatedExchanger is a Provider that can complete a sign-in the provider started (AllowProviderInitiated): a code that arrives with no state and no PKCE verifier, trusted only through the verified ID token of its token response. *OAuth2 (with an IDToken key set) and *OIDCProvider implement it.
type IssuerChecker ¶
type IssuerChecker interface {
Provider
// ExpectedIssuer is the iss every authorization response must carry, or
// "" when the provider sends none.
ExpectedIssuer() string
}
IssuerChecker is a Provider whose authorization responses carry the RFC 9207 iss parameter. *OAuth2 (AuthorizationIssuer) and *OIDCProvider (from discovery) implement it; the callback refuses a response whose iss is missing or another when AuthorizationIssuer is not "".
type Labeler ¶
type Labeler interface {
Provider
// Label is the display name; "" falls back to Name.
Label() string
}
Labeler is a Provider with a name for people, shown on a sign-in button ("Google", "Okta at Acme"). *OAuth2 and *OIDCProvider implement it through their DisplayName; a Provider that does not is listed under its Name.
type Loader ¶
type Loader interface {
Provider
// Load fetches what the provider would otherwise fetch at its first
// flow, and reports why it cannot be used.
Load(ctx context.Context, hc *http.Client) error
}
Loader is a Provider that reads part of its configuration lazily (a discovery document, a key set). SkipBrokenProviders calls Load on every one at auth.New, so a provider that cannot be loaded is found and left out then rather than failing its first flow. *OAuth2 and *OIDCProvider implement it; a custom Provider that is not a Loader is checked on its name alone.
type LogoutRequest ¶
type LogoutRequest struct {
// Provider names the provider to sign out of on the general logout
// route (POST /logout), which needs it (ErrLogoutProvider). On a
// provider's own route (/<provider>/logout) it may be left empty; any
// other value than that provider is ErrLogoutProvider.
Provider string `json:"provider,omitempty"`
// ReturnTo is where the provider sends the browser after its logout, as
// post_logout_redirect_uri, instead of PostLogoutURL. It passes the same
// TrustsReturnURL check as a sign-in's returnTo, so it is a path on this
// site, a URL on a trusted origin or a trusted deep-link scheme;
// anything else is ErrBadReturnTo and the session is not ended. A path
// is made absolute against the base URL, because the parameter must be
// an absolute URI. It must still be registered at the provider, which
// refuses an unregistered one on its own page.
ReturnTo string `json:"returnTo,omitempty"`
}
LogoutRequest is the input of a provider logout. Every field is optional: an empty request sends the configured PostLogoutURL.
type LogoutResult ¶
type LogoutResult struct {
URL string `json:"url"`
}
LogoutResult is the provider URL that ends the session there. HTTP transports redirect to it; a Go or API caller navigates itself.
type Logouter ¶
type Logouter interface {
Provider
// EndSession returns the provider's end_session_endpoint, or "" when the
// provider has none.
EndSession(ctx context.Context, hc *http.Client) (string, error)
}
Logouter is a Provider with an RP-initiated logout endpoint (OpenID Connect RP-Initiated Logout 1.0). *OAuth2 (EndSessionURL) and *OIDCProvider (the discovery document's end_session_endpoint) implement it; the logout operation exists only for providers that do.
type MicrosoftPhotoSize ¶
type MicrosoftPhotoSize int
MicrosoftPhotoSize is a size Microsoft Graph serves a profile photo at (better-auth's profilePhotoSize). A closed set: Graph answers 404 for any other.
const ( MicrosoftPhoto48 MicrosoftPhotoSize = 48 MicrosoftPhoto64 MicrosoftPhotoSize = 64 MicrosoftPhoto96 MicrosoftPhotoSize = 96 MicrosoftPhoto120 MicrosoftPhotoSize = 120 MicrosoftPhoto240 MicrosoftPhotoSize = 240 MicrosoftPhoto360 MicrosoftPhotoSize = 360 MicrosoftPhoto432 MicrosoftPhotoSize = 432 MicrosoftPhoto504 MicrosoftPhotoSize = 504 MicrosoftPhoto648 MicrosoftPhotoSize = 648 )
The sizes Graph serves, in pixels per side.
type OAuth2 ¶
type OAuth2 struct {
ID string
ClientID string
ClientSecret string
AuthURL string
TokenURL string
Scopes []string
// FetchProfile reads the profile with the access token.
FetchProfile func(ctx context.Context, hc *http.Client, accessToken string) (Profile, error)
// FetchProfileFromTokens reads the profile from the whole token set
// (TokenSet.Raw included), for a provider whose profile call needs more
// than the access token. One profile source is required: this,
// FetchProfile, or IDToken.ProfileFromToken; the first set wins in that
// order reversed (ProfileFromToken, then this, then FetchProfile).
FetchProfileFromTokens func(ctx context.Context, hc *http.Client, tokens TokenSet) (Profile, error)
// TokenAuth is how the token endpoint is authenticated to; zero is ClientSecretPost.
TokenAuth TokenAuth
// AuthParams are added to every authorization URL (prompt, access_type,
// hd, …); the protocol's own keys (reservedAuthParams) are ignored, so a
// parameter here can never weaken the flow.
AuthParams url.Values
// TokenParams are added to every token and refresh request; the grant's
// own keys (reservedTokenParams) are ignored.
TokenParams url.Values
// RefreshParams are added to refresh requests only, on top of
// TokenParams, for a provider whose refresh grant takes a parameter the
// code exchange does not; the grant's own keys are ignored here too.
RefreshParams url.Values
// AuthorizationIssuer is the iss value the callback must carry (RFC
// 9207), for a provider that sends one; a callback without it, or with
// another, is refused as ErrStateInvalid, the mix-up attack defence.
// Empty means the provider sends none and nothing is checked. An
// OIDCProvider fills it from the discovery document when the issuer
// publishes authorization_response_iss_parameter_supported.
AuthorizationIssuer string
// RevokeURL is the provider's token revocation endpoint (RFC 7009); when
// set and StoreTokens kept tokens, unlink revokes them there, best
// effort. Empty revokes nothing.
RevokeURL string
// ClientAssertion signs the RFC 7523 client assertion for TokenAuth
// PrivateKeyJWT; see the ClientAssertion constructor. Validate requires
// it with PrivateKeyJWT and refuses it with any other TokenAuth.
ClientAssertion func(ctx context.Context, tokenURL string) (string, error)
// OptionalScopes are the scopes a start request may add to Scopes for
// incremental authorization; any other is refused with ErrBadScope.
OptionalScopes []string
// OptionalParams are the authorization parameters a start request may
// set (StartRequest.Params): better-auth's per-request additionalParams,
// such as display, ui_locales or acr_values. Only names listed here are
// forwarded, any other is refused with ErrBadParam, and the protocol's
// own (reservedAuthParams) are never forwarded, so a client can never
// choose a parameter the app did not name.
OptionalParams []string
// TokenLifetime is the access token's lifetime when the token response
// has no expires_in; zero keeps the expiry unknown, which disables refresh.
TokenLifetime time.Duration
// IDToken makes the provider accept ID tokens (the id-token operation)
// and, with ProfileFromToken, read the profile from the token response's
// id_token instead of FetchProfile. Zero accepts none.
IDToken IDTokenConfig
// EndSessionURL is the provider's RP-initiated logout endpoint; empty
// means none, and the logout operation answers ErrLogoutUnsupported.
EndSessionURL string
// ClientSecretFunc computes the client secret per token request, for a
// secret that is a signed assertion (Apple; see AppleClientSecret).
ClientSecretFunc func(ctx context.Context) (string, error)
// Headers are added to every token, refresh and revocation request, for
// a provider that requires a User-Agent naming the app (Reddit) or an
// API version header. Authorization and Content-Type (reservedHeaders)
// are ignored: the credentials and the encoding are the grant's own.
Headers http.Header
// FormPost asks the provider to answer with response_mode=form_post
// (OAuth 2.0 Form Post Response Mode): the callback arrives as a
// cross-site POST, which the plugin relays into the GET callback. Apple
// requires it whenever a scope is requested; see AppleWithEmail.
FormPost bool
// PostedUser reads the user field a form-post callback may carry into
// the profile after the exchange: Apple sends the person's name there,
// once, on first consent, and nowhere else. Nil ignores the field.
PostedUser func(raw string, p *Profile)
// GetToken, when set, replaces the built-in code exchange: it receives
// the callback's code, the flow's PKCE verifier ("" on a
// provider-initiated sign-in, which has none) and the redirect URI, and
// returns the provider's tokens. For a provider whose token endpoint is
// not RFC 6749 (a GET, a JSON body, another parameter name). Everything
// else stays the plugin's: the state, PKCE, the profile read from the
// returned tokens (FetchProfile, FetchProfileFromTokens or the ID token),
// linking and the session. An error it returns comes out of Exchange
// unchanged; the callback logs it and answers ErrProviderFailed, as for
// every failed exchange. A set without an AccessToken is a failed
// exchange, and TokenLifetime fills a zero ExpiresAt. With it, TokenURL
// is needed only for Refresh.
GetToken func(ctx context.Context, code, verifier, redirectURI string) (TokenSet, error)
// DisplayName is the provider's name for people ("Google", "Okta at
// Acme"), listed by the providers route for a sign-in page's buttons.
// The presets set it; empty falls back to ID.
DisplayName string
// NoPKCE leaves PKCE out of this provider's flow: no code_challenge on
// the authorization URL and no code_verifier at the exchange. better-auth's
// pkce: false, for a provider whose web flow has no PKCE and might refuse
// or misread the parameters (Notion, TikTok's web Login Kit, WeChat). The
// state is still single use and bound to the browser by the flow cookie,
// and the client secret still guards the exchange; what is lost is RFC
// 7636's defence against an authorization code stolen in transit and
// injected into another browser's callback, the accepted risk of setting
// it. Validate refuses it with PublicClient, where PKCE is the only thing
// that ties the code to this client. The verifier is still generated, so
// an IDToken.ProfileFromToken nonce keeps its binding to the flow.
NoPKCE bool
// ClientIDParam is the name the client id travels under on the
// authorization URL and in the token, refresh and revocation forms;
// empty is client_id (RFC 6749). TikTok calls it client_key (better-auth's
// clientKey option), WeChat appid.
ClientIDParam string
// ClientSecretParam is the name of the client secret in the token,
// refresh and revocation forms under ClientSecretPost; empty is
// client_secret. WeChat calls it secret.
ClientSecretParam string
// ScopeSeparator joins the scopes in the scope parameter; empty is a
// space (RFC 6749 section 3.3). TikTok and WeChat take a comma.
ScopeSeparator string
// AuthURLFragment is appended to the authorization URL as #fragment, for
// a provider that requires one (WeChat's #wechat_redirect). Empty adds
// none.
AuthURLFragment string
// CallbackParams are callback query parameters the token request
// carries as well, for a provider whose exchange needs a value its
// redirect returned: VK ID requires the device_id and the state it sent
// back. Each must be one of CallbackParamValues; Validate refuses any
// other. Empty forwards none, the RFC 6749 exchange.
CallbackParams []CallbackParam
// RefreshCallbackParams are callback values the refresh grant needs
// again, long after the callback: VK ID's device_id. The exchange puts
// them in TokenSet.RefreshParams, StoreTokens keeps them sealed with the
// tokens, and RefreshWithParams sends them. Each must be one of
// CallbackParamValues.
RefreshCallbackParams []CallbackParam
// contains filtered or unexported fields
}
OAuth2 is a provider that speaks plain OAuth 2.0 with PKCE and reads the profile with the access token. The URL fields are exported so a test can point them at a local server.
func Apple ¶
func Apple(clientID string, secret func(ctx context.Context) (string, error), audiences ...string) *OAuth2
Apple is Sign in with Apple. The profile comes from the ID token Apple's token endpoint returns (Apple has no userinfo endpoint), verified against Apple's keys, and the same configuration accepts ID tokens from the iOS SDK through the id-token operation: audiences lists the native app's bundle ids. secret is AppleClientSecret, since Apple's client secret is a signed assertion, not a string.
Limits: no scopes are requested, so the web flow yields the subject without an address or a name, and nothing arrives by POST; the native flow's ID token carries the email when the user shared it. AppleWithEmail is the same provider with the email and name scopes, which Apple answers by form post.
func AppleWithEmail ¶
func AppleWithEmail(clientID string, secret func(ctx context.Context) (string, error), audiences ...string) *OAuth2
AppleWithEmail is Apple with the email and name scopes. Apple answers such a flow with response_mode=form_post, so the provider sets FormPost: the callback arrives as a cross-site POST at the callback URL, which the plugin relays into its GET callback; register the same callback URL at Apple as for Apple. The email comes from the ID token (a private relay address when the person hid theirs), the name from the user field Apple sends once, on first consent, read by ApplePostedUser; both reach the profile and the hooks. The native flow is unchanged.
func Atlassian ¶
Atlassian is Atlassian account sign-in (Jira, Confluence): scopes read:me, read:account and offline_access, profile from /me, email verified when Atlassian says so.
func Cloudflare ¶
Cloudflare is Cloudflare sign-in: scope user-details.read with a Basic-authenticated token request, profile from /client/v4/user. The answer has no verified flag, so the email is unverified.
func Discord ¶
Discord is Discord sign-in: scopes identify and email, profile from /users/@me; the email is verified when Discord says so.
func Dropbox ¶
Dropbox is Dropbox sign-in: scope account_info.read, profile from get_current_account (a POST), email verified when Dropbox says so.
func Dub ¶
Dub is Sign in with Dub, better-auth's dub plugin oauth option: PKCE (Dub enables it by default) and the user.read scope. The profile is /oauth/userinfo: id, name and image, no email, so a Dub sign-up creates a user with no address; the workspace the user authorized comes with it in Profile.Claims ("workspace"). Add Dub's API scopes (links.read, …) with Scopes or OptionalScopes and keep the tokens with StoreTokens to call the Dub API for the user.
func Facebook ¶
Facebook is Facebook Login: scopes email and public_profile, profile from /me. The Graph API has no verified flag for the email, so it is unverified; TrustedProviders("facebook") trusts it.
func Figma ¶
Figma is Figma sign-in: scope current_user:read with a Basic-authenticated token request, profile from /v1/me; Figma has no verified flag, so the email is unverified.
func GitHub ¶
GitHub is GitHub sign-in. The address comes from /user/emails, where GitHub says which address is primary and verified; the public profile email is neither.
func Gumroad ¶
Gumroad is Gumroad sign-in: scope view_profile, profile from /v2/user; Gumroad has no verified flag, so the email is unverified.
func HubSpot ¶
HubSpot is HubSpot sign-in: scope oauth, identity from the access-token introspection endpoint (user id and email; HubSpot has no name or picture there). The email is unverified: HubSpot does not say.
func Instagram ¶
Instagram is sign-in with an Instagram professional (business or creator) account through the Instagram API with Instagram Login, as better-auth-instagram configures it: scopes comma-joined (instagram_business_basic), no PKCE (NoPKCE; Instagram documents none), and a token answer that is not RFC 6749: Instagram documents it wrapped in a data array ({"data":[{"access_token","user_id","permissions"}]}) and has answered it flat, so the exchange reads either. The token is the one-hour short-lived token (InstagramTokenLifetime) and cannot be refreshed: the long-lived exchange is a separate Graph API call the app makes with the stored token when it needs one. Instagram sends no email, so a sign-up creates a user with no address; better-auth-instagram invents "<id>@instagram.com" instead, which this preset does not, because an address nobody receives mail at would be verified-looking data that is false. "force_reauth" is an allowed per-request parameter (StartRequest.Params).
func Kakao ¶
Kakao is Kakao Login: scopes account_email, profile_image and profile_nickname, profile from /v2/user/me, email verified when Kakao says so.
func Kick ¶
Kick is Kick sign-in: scope user:read, profile from /public/v1/users; Kick has no verified flag, so the email is unverified.
func Linear ¶
Linear is Linear sign-in: scope read, profile through the GraphQL viewer query. Linear accounts are email-verified by Linear, but its API has no flag, so the email is unverified here; TrustedProviders("linear") trusts it.
func Microsoft ¶
Microsoft is Microsoft Entra ID (work, school and personal accounts). The endpoints are static rather than discovered because the multi-tenant aliases publish a templated issuer the discovery check would refuse. The subject is the oid claim. Graph's userinfo has no email_verified, so the email is unverified: name the provider in TrustedProviders to link by it. With a single tenant the preset also accepts ID tokens (MSAL on mobile).
func Naver ¶
Naver is Naver Login: the profile fields are chosen in the Naver console rather than by scope, and come from /v1/nid/me. Naver has no verified flag, so the email is unverified.
func Notion ¶
Notion is Sign in with Notion for a public integration: owner=user on the authorization URL and no scopes; the token request is HTTP Basic with a JSON body, as Notion documents it; the profile is the token's owner read from /v1/users/me (bot.owner.user), its email unverified. PKCE is off (NoPKCE): Notion documents none, and better-auth sends none.
func Patreon ¶
Patreon is Patreon sign-in: scopes identity and identity[email], profile from the v2 identity endpoint, email verified when Patreon says so.
func PayPal ¶
PayPal is Log in with PayPal: scopes openid, profile and email with a Basic-authenticated token request, profile from the OpenID userinfo endpoint (subject user_id). sandbox switches the three endpoints to the sandbox environment.
func Polar ¶
Polar is Polar sign-in: scopes openid, profile and email, profile from the OpenID-shaped userinfo endpoint.
func Reddit ¶
Reddit is Reddit sign-in: scope identity with a Basic-authenticated token request, profile from /api/v1/me. Reddit's API refuses the default Go user agent, so userAgent names the app ("web:com.acme.app:v1.0 (by /u/acme)" is Reddit's recommended form); it is sent on the profile call. Reddit returns no email address.
func Snapchat ¶
Snapchat is Login with Snapchat (Login Kit), as better-auth-snapchat configures it: the scopes user.external_id, user.display_name and user.bitmoji.avatar as Snapchat's full scope URLs (SnapchatScopePrefix), the token endpoint with HTTP Basic as Snapchat documents it, PKCE S256 on, revocation of the refresh token at unlink, and the profile from Snap Kit's /v1/me: the externalId as subject, the display name and the Bitmoji avatar. Snapchat sends no email, so a sign-up creates a user with no address; better-auth-snapchat invents "<id>@snapchat.com", which this preset does not, for the reason Instagram gives.
func Spotify ¶
Spotify is Spotify sign-in: scope user-read-email, profile from /v1/me; Spotify has no verified flag, so the email is unverified.
func TikTok ¶
TikTok is Login Kit for Web. clientKey is the app's client key, TikTok's name for the client id (better-auth's clientKey), sent as client_key on the authorization URL and in the token, refresh and revocation forms. Scope user.info.basic, comma-joined; the subject is open_id; TikTok has no email, so the user is created with no identity. PKCE is off (NoPKCE): the web flow documents none, and better-auth sends no challenge either. Unlink revokes the token at TikTok's revoke endpoint.
func VK ¶
VK is Sign in with VK ID: PKCE S256, scopes email and phone, and a token request that carries the device_id and the state the callback returned (CallbackParams), as VK ID requires. The profile is read by posting the access token and client id to /oauth2/user_info; the email is unverified. VK ID's refresh grant also requires the device_id: it is kept, sealed, with the stored tokens (RefreshCallbackParams, under StoreTokens) and sent with every refresh.
func Vercel ¶
Vercel is Sign in with Vercel: scopes openid, profile and email, profile from the OpenID-shaped userinfo endpoint.
func WeChat ¶
WeChat is WeChat Login for a website app (QR code): appid instead of client_id, scope snsapi_login, #wechat_redirect on the authorization URL, lang on the QR page (WeChatLangCN, better-auth's default, or WeChatLangEN). The token and refresh endpoints are GETs that take appid and secret in the query, as WeChat documents them; the secret therefore travels in a URL, which this provider cannot avoid. The subject is the unionid when WeChat sends one, else the openid; WeChat has no email. PKCE is off (NoPKCE): WeChat has none.
func X ¶
X is Sign in with X (Twitter): PKCE with a Basic-authenticated token request, scopes users.read, tweet.read, offline.access and users.email, profile from /2/users/me. The email is the account's confirmed address, so it is verified when present.
func Yandex ¶
Yandex is Yandex ID sign-in: scopes login:info, login:email and login:avatar, profile from login.yandex.ru/info with the OAuth authorization scheme. Yandex has no verified flag, so the email is unverified.
func Zoom ¶
Zoom is Zoom sign-in: a Basic-authenticated token request and the profile from /v2/users/me; scopes are set on the app in the Zoom marketplace.
func (*OAuth2) ApplyPosted ¶
ApplyPosted runs PostedUser on p when the provider has one and the callback carried a user field.
func (*OAuth2) AuthCodeURL ¶
AuthCodeURL builds the authorization URL. An AuthURL that already carries a query, as some tenants' endpoints do, keeps it.
func (*OAuth2) AuthCodeURLWithParams ¶
func (o *OAuth2) AuthCodeURLWithParams(state, challenge, redirectURI string, rp RequestParams) string
AuthCodeURLWithParams builds the authorization URL with the provider's AuthParams and the request's parameters. Protocol parameters win over both, so neither can replace the state or the challenge.
func (*OAuth2) EndSession ¶
EndSession returns EndSessionURL.
func (*OAuth2) Exchange ¶
func (o *OAuth2) Exchange(ctx context.Context, hc *http.Client, code, verifier, redirectURI string) (Profile, error)
Exchange trades the code for an access token and reads the profile.
func (*OAuth2) ExchangeInitiated ¶
func (o *OAuth2) ExchangeInitiated(ctx context.Context, hc *http.Client, code, redirectURI string) (Profile, error)
ExchangeInitiated implements InitiatedExchanger: the code is exchanged with no verifier, the ID token verified with no nonce allowed, and the profile read as Exchange reads it, whose subject must be the token's.
func (*OAuth2) ExpectedIssuer ¶
ExpectedIssuer returns the AuthorizationIssuer field.
func (*OAuth2) FetchProfileWith ¶
func (o *OAuth2) FetchProfileWith(ctx context.Context, hc *http.Client, ts TokenSet) (Profile, error)
FetchProfileWith runs FetchProfileFromTokens, else FetchProfile with the access token.
func (*OAuth2) Load ¶
Load fetches the ID-token key set when the provider accepts ID tokens; a provider without one has nothing to load.
func (*OAuth2) OptionalParamList ¶
OptionalParamList returns OptionalParams.
func (*OAuth2) OptionalScopeList ¶
OptionalScopeList returns OptionalScopes.
func (*OAuth2) PostsBack ¶
PostsBack reports FormPost: the authorization response arrives as a POST.
func (*OAuth2) Refresh ¶
func (o *OAuth2) Refresh(ctx context.Context, hc *http.Client, refreshToken auth.Token) (TokenSet, error)
Refresh runs the refresh_token grant. A provider that does not rotate refresh tokens answers without one; the caller keeps the old one then.
func (*OAuth2) RefreshWithParams ¶
func (o *OAuth2) RefreshWithParams(ctx context.Context, hc *http.Client, refreshToken auth.Token, params url.Values) (TokenSet, error)
RefreshWithParams is Refresh that also sends params, the account's stored TokenSet.RefreshParams (VK ID's device_id); the protocol's own keys in it are ignored. The parameters stay with the new token set.
func (*OAuth2) Revoke ¶
Revoke posts the token to RevokeURL with the client credentials, per RFC 7009 section 2.1. The RFC makes an unknown token a success (200), so only a transport failure or another status is an error.
type OIDCProvider ¶
type OIDCProvider struct {
ID string
Issuer string
ClientID string
ClientSecret string
Scopes []string
// TokenAuth, AuthParams, TokenParams, RefreshParams, OptionalScopes,
// OptionalParams, TokenLifetime and ClientAssertion mean what they mean on OAuth2; they
// are copied onto the discovered endpoints.
TokenAuth TokenAuth
AuthParams url.Values
TokenParams url.Values
RefreshParams url.Values
OptionalScopes []string
OptionalParams []string
TokenLifetime time.Duration
ClientAssertion func(ctx context.Context, tokenURL string) (string, error)
// SubjectClaim is the userinfo claim taken as the account's subject
// instead of sub, for an issuer whose stable identifier is another claim
// (Entra ID's oid, which survives a tenant's app re-registration where
// sub does not). Empty is sub.
SubjectClaim string
// RevokeURL overrides the discovery document's revocation_endpoint;
// empty takes the discovered one, and unlink revokes stored tokens there.
RevokeURL string
// IDToken configures ID-token sign-in. JWKSURL and Issuers are filled
// from the discovery document (jwks_uri and the configured issuer) when
// left empty, so an OIDC provider accepts ID tokens with no extra field;
// Audiences, ProfileFromToken and RequireNonce are copied as given.
IDToken IDTokenConfig
// EndSessionURL overrides the discovery document's end_session_endpoint;
// empty takes the discovered one.
EndSessionURL string
// Headers means what it means on OAuth2: added to every token, refresh
// and revocation request at the discovered endpoints.
Headers http.Header
// DiscoveryHeaders are added to the discovery request, for an issuer
// behind a gateway that wants a header on it; Authorization is ignored,
// since a discovery document is public and takes no credential.
DiscoveryHeaders http.Header
// GetToken means what it means on OAuth2: it replaces the code exchange
// at the discovered token endpoint.
GetToken func(ctx context.Context, code, verifier, redirectURI string) (TokenSet, error)
// DisplayName is the provider's name for people, as on OAuth2; the
// presets set it, and empty falls back to ID.
DisplayName string
// Fallback are static endpoints used ONLY while the discovery document
// cannot be fetched (a transport error, a status other than 2xx, a body
// that is not JSON), so an issuer whose discovery is down still signs
// people in. A document that arrives but names another issuer or lacks
// an endpoint is still refused: the issuer answered, and what it said is
// wrong. Each use is logged at Warn on Logger, and discovery is tried
// again after discoveryRetryInterval. The zero value has no fallback: a
// failed discovery fails that flow with ErrProviderFailed, as before.
Fallback Endpoints
// Logger receives the fallback warning; nil is slog.Default(). The oauth
// plugin sets it to the core's logger when it is nil.
Logger *slog.Logger
// contains filtered or unexported fields
}
OIDCProvider is any OpenID Connect issuer, configured from its discovery document: Okta, Auth0, Keycloak, Entra ID, Zitadel. The profile comes from the userinfo endpoint with the access token, which the specification makes authoritative, so no ID-token signature check is needed on this path.
It discovers lazily at the first flow and caches the result on the value; a failed discovery is retried at the next flow rather than kept.
func Auth0 ¶
func Auth0(domain, clientID, clientSecret string) *OIDCProvider
Auth0 is an Auth0 tenant through OpenID Connect; domain is the tenant domain such as "acme.us.auth0.com", with or without the scheme.
func Cognito ¶
func Cognito(region, userPoolID, clientID, clientSecret string) *OIDCProvider
Cognito is an Amazon Cognito user pool through OpenID Connect: the pool's discovery document publishes its hosted UI as the authorization endpoint.
func GitLab ¶
func GitLab(issuer, clientID, clientSecret string) *OIDCProvider
GitLab is GitLab sign-in through OpenID Connect; issuer is GitLabIssuer or a self-hosted instance.
func HuggingFace ¶
func HuggingFace(clientID, clientSecret string) *OIDCProvider
HuggingFace is Hugging Face sign-in through OpenID Connect.
func Keycloak ¶
func Keycloak(issuer, clientID, clientSecret string) *OIDCProvider
Keycloak is a Keycloak realm through OpenID Connect; issuer is "https://<host>/realms/<realm>".
func LINE ¶
func LINE(clientID, clientSecret string) *OIDCProvider
LINE is LINE Login through OpenID Connect; the email scope needs LINE's approval for the channel.
func LinkedIn ¶
func LinkedIn(clientID, clientSecret string) *OIDCProvider
LinkedIn is Sign in with LinkedIn through OpenID Connect; the product "Sign In with LinkedIn using OpenID Connect" must be enabled on the app.
func OIDC ¶
func OIDC(name, issuer, clientID, clientSecret string) *OIDCProvider
OIDC builds a provider for issuer. name is the route segment and the stored provider name, such as "okta".
func Okta ¶
func Okta(issuer, clientID, clientSecret string) *OIDCProvider
Okta is an Okta org or authorization server through OpenID Connect; issuer is "https://<org>.okta.com" or "https://<org>.okta.com/oauth2/<server>".
func Paybin ¶
func Paybin(clientID, clientSecret string) *OIDCProvider
Paybin is Sign in with Paybin through OpenID Connect.
func Railway ¶
func Railway(clientID, clientSecret string) *OIDCProvider
Railway is Railway sign-in through OpenID Connect; the userinfo endpoint is its /oauth/me.
func Roblox ¶
func Roblox(clientID, clientSecret string) *OIDCProvider
Roblox is Roblox sign-in through OpenID Connect. Roblox has no email scope, so the profile carries the subject, name and picture only.
func Salesforce ¶
func Salesforce(issuer, clientID, clientSecret string) *OIDCProvider
Salesforce is Salesforce sign-in through OpenID Connect; issuer is SalesforceIssuer, SalesforceSandboxIssuer or a My Domain.
func Slack ¶
func Slack(clientID, clientSecret string) *OIDCProvider
Slack is Sign in with Slack through OpenID Connect.
func Twitch ¶
func Twitch(clientID, clientSecret string) *OIDCProvider
Twitch is Twitch sign-in through OpenID Connect, with the claims request Twitch needs before its userinfo endpoint sends the email, picture and username.
func Zitadel ¶
func Zitadel(issuer, clientID, clientSecret string) *OIDCProvider
Zitadel is a Zitadel instance through OpenID Connect; issuer is the instance's URL.
func Zoho ¶
func Zoho(region ZohoRegion, clientID, clientSecret string) *OIDCProvider
Zoho is Sign in with Zoho through OpenID Connect at region's accounts server, as better-auth-zoho configures it (discovery, scopes openid, email and profile). Use the data centre the app was registered in.
func (*OIDCProvider) AuthCodeURL ¶
func (o *OIDCProvider) AuthCodeURL(state, challenge, redirectURI string) string
AuthCodeURL builds the authorization URL from the discovered endpoint. It returns "" before discovery; the plugin always discovers first.
func (*OIDCProvider) AuthCodeURLWithParams ¶
func (o *OIDCProvider) AuthCodeURLWithParams(state, challenge, redirectURI string, rp RequestParams) string
AuthCodeURLWithParams is AuthCodeURL with the request's parameters.
func (*OIDCProvider) Discover ¶
Discover reads the discovery document once. It refuses a document whose issuer is not the configured one, the check the specification requires so a redirected discovery URL cannot hand the flow to another issuer.
func (*OIDCProvider) EndSession ¶
EndSession returns the issuer's end_session_endpoint, discovering first; "" when the issuer publishes none.
func (*OIDCProvider) Exchange ¶
func (o *OIDCProvider) Exchange(ctx context.Context, hc *http.Client, code, verifier, redirectURI string) (Profile, error)
Exchange trades the code for the profile through the discovered endpoints.
func (*OIDCProvider) ExchangeInitiated ¶
func (o *OIDCProvider) ExchangeInitiated(ctx context.Context, hc *http.Client, code, redirectURI string) (Profile, error)
ExchangeInitiated discovers the issuer and exchanges through its endpoints; an issuer whose document has no jwks_uri cannot verify the ID token and fails.
func (*OIDCProvider) ExpectedIssuer ¶
func (o *OIDCProvider) ExpectedIssuer() string
ExpectedIssuer returns the iss the callback must carry: the configured issuer when the discovery document says it is sent, "" otherwise or before discovery (the plugin discovers at start, before any callback).
func (*OIDCProvider) FetchProfileWith ¶
func (o *OIDCProvider) FetchProfileWith(ctx context.Context, hc *http.Client, ts TokenSet) (Profile, error)
FetchProfileWith reads the userinfo endpoint with the tokens' access token.
func (*OIDCProvider) Label ¶
func (o *OIDCProvider) Label() string
Label returns DisplayName, or ID when it is empty.
func (*OIDCProvider) Load ¶
Load discovers the issuer (a Fallback in use counts) and fetches the key set it names.
func (*OIDCProvider) Name ¶
func (o *OIDCProvider) Name() string
Name returns ID, the route segment and stored provider name.
func (*OIDCProvider) OptionalParamList ¶
func (o *OIDCProvider) OptionalParamList() []string
OptionalParamList returns OptionalParams.
func (*OIDCProvider) OptionalScopeList ¶
func (o *OIDCProvider) OptionalScopeList() []string
OptionalScopeList returns OptionalScopes.
func (*OIDCProvider) Refresh ¶
func (o *OIDCProvider) Refresh(ctx context.Context, hc *http.Client, refreshToken auth.Token) (TokenSet, error)
Refresh runs the refresh grant at the discovered token endpoint.
func (*OIDCProvider) Revoke ¶
Revoke revokes the tokens at the discovered revocation_endpoint; nothing when the issuer publishes none.
func (*OIDCProvider) VerifyIDToken ¶
func (o *OIDCProvider) VerifyIDToken(ctx context.Context, hc *http.Client, token, nonce string) (Profile, error)
VerifyIDToken implements IDTokenVerifier for an OpenID Connect issuer: the key set is the discovery document's jwks_uri and the issuer the configured one, so no field needs filling by hand.
type Option ¶
type Option func(*Plugin)
Option configures the plugin; pass options to New.
func AllowIDTokenReplay ¶
func AllowIDTokenReplay() Option
AllowIDTokenReplay turns ID-token replay protection off: a captured token then signs in, or re-authenticates its user's session, as often as its holder likes until it expires (minutes to an hour at the providers). For an app whose client legitimately posts one token twice and cannot ask the SDK for a new one. auth.New logs WarnIDTokenReplayAllowed when it is set.
func AllowProviderInitiated ¶
AllowProviderInitiated lets the named providers start a sign-in themselves: a tile on the company's identity dashboard (Okta, Entra ID, Google Workspace) that sends the browser to the callback with a code and no state. Off by default for every provider, and only for providers that verify ID tokens (*OIDCProvider, or *OAuth2 with an IDToken key set; auth.New refuses any other with ErrProviderInitiatedConfig).
On that path the code is exchanged without PKCE, and the token response must carry an ID token whose signature, issuer, audience and lifetime verify; it must carry no nonce (one would mean it was issued for another flow), and its subject must be the profile's. The person is signed in only as the user already linked to that provider subject, or as a new user; an address that matches an existing account is ErrAccountExists, never a link, and a signed-in browser is signed out first, never linked. It lands on the site root.
The risk, which this switch accepts: without a state bound to the browser, anyone who holds a code for their own account at the provider can send someone else's browser to the callback with it and sign that browser in as themselves (login forgery). The victim then works inside the attacker's account. Enable it only for a provider whose dashboard people actually use, and keep it off for consumer providers. RestartProviderInitiated is the mode without that risk, at the price of a second trip to the provider.
func AllowUnlinkingAll ¶
func AllowUnlinkingAll() Option
AllowUnlinkingAll lets a user unlink the last provider account even when nothing else signs them in, better-auth's accountLinking.allowUnlinkingAll. Off by default: unlink refuses to remove the last sign-in method, because a user who does it cannot sign in again and only an account recovery (a password reset to a verified address, an admin) brings them back. For apps where signing in again is a fresh sign-up by design.
func CallbackURL ¶
CallbackURL sets the redirect URI registered at each provider. The default is BaseURL + DefaultMountPrefix + "/oauth/<provider>/callback".
func ErrorURL ¶
ErrorURL sends the browser to path, a page on this site, when a callback fails, with the error code in the query (`?error=oauth.denied`), instead of answering the error as JSON. For apps whose sign-in page shows the message; a request may name another page with StartRequest.ErrorTo. Without it, callbacks answer errors as JSON, as every operation does.
func ExplicitSignUp ¶
ExplicitSignUp makes a callback create a user only when the request asked for it (StartRequest.SignUp, IDTokenRequest.SignUp); a sign-in for a provider account nobody holds is ErrSignUpNotRequested. For apps with separate "sign in" and "sign up" buttons, where a person who meant to sign in should not end up with a second account. Linked accounts, verified-email matches and link flows are unaffected. With provider names, only those providers need the flag; with none, all do. NoSignUp wins where both apply.
func ExposeAccessTokens ¶
func ExposeAccessTokens() Option
ExposeAccessTokens adds POST /access-token {"provider"}: the signed-in user's own access token at that provider, refreshed when it is within RefreshMargin of expiring, with its expiry, granted scopes and ID token. better-auth's getAccessToken from the client, for a browser or mobile app that calls the provider's API itself (Google Drive from the page). Off by default, because it moves a bearer credential for another service into the browser: any script that runs on the app's origin (one XSS) can then read it and call the provider as the user until it expires. The refresh token is never sent; the route needs a signed-in session and the origin check, and requires StoreTokens (ErrExposeNeedsStoreTokens).
func LinkUnverifiedLocalEmail ¶
func LinkUnverifiedLocalEmail() Option
LinkUnverifiedLocalEmail is better-auth's accountLinking.requireLocalEmailVerified: false. The implicit link by email then also links a provider account with a verified address to a local account holding that address unverified, instead of refusing with ErrAccountExists. The provider side must still be verified (or trusted). Off by default; Init logs WarnLinkUnverifiedLocal at every start while it is on.
The attack it opens is account pre-hijacking: anyone can register the victim's address with a password of their own and leave it unverified; when the victim later signs in with their provider, the provider account is linked to that local account, and the attacker's password still signs in to it, now holding the victim's data. Use it only where nobody can create an account with an address they do not control (no self sign-up, or every sign-up verified before any credential is set).
func NoImplicitLinking ¶
func NoImplicitLinking() Option
NoImplicitLinking turns off the automatic link by verified email: a provider account whose address matches an existing user is refused with ErrAccountExists even when both sides are verified, and the person links from settings after signing in the usual way. For apps that want every link to be a deliberate act.
func NoSignUp ¶
NoSignUp stops callbacks from creating users: a provider account that is already linked signs in, a verified address that matches a verified identity links and signs in, a signed-in user may link, and anything else is ErrSignUpDisabled. For apps where accounts are invited, not opened. With provider names, only those providers are closed to sign-up; with none, all are.
func OnResolved ¶
func OnResolved(fn ResolvedHook) Option
OnResolved adds a ResolvedHook; several run in order.
func PostLogoutURL ¶
PostLogoutURL sets the post_logout_redirect_uri sent with a provider logout, per provider; it must be registered at the provider. The default is the base URL. A logout request's own ReturnTo, once it passes TrustsReturnURL, replaces it for that request.
func RequireEmail ¶
func RequireEmail() Option
RequireEmail refuses a sign-in or sign-up whose provider profile carries no email at all (ErrEmailNotFound), verified or not, so every user this plugin creates can be reached and can recover the account. A link flow by a signed-in user is not affected.
func RequireSameEmailOnLink ¶
func RequireSameEmailOnLink() Option
RequireSameEmailOnLink refuses a link flow whose provider account carries an address that is not one of the signed-in user's identities (ErrEmailMismatch), so a settings page cannot attach a stranger's account by mistake. Off by default: linking from a signed-in session links whatever the provider returns.
func RequireVerifiedEmail ¶
func RequireVerifiedEmail() Option
RequireVerifiedEmail refuses a sign-in or sign-up whose provider profile has no email or an unverified one (ErrEmailUnverified), so no session is ever issued on an address the provider does not vouch for. A link flow by a signed-in user is not affected: the user is already proven.
func RestartProviderInitiated ¶
RestartProviderInitiated is the other answer to a provider-initiated sign-in, better-auth's allowIdpInitiated: a callback with a code and no state, for the named providers, discards the code and starts an ordinary flow in its place (a fresh state bound to the browser, PKCE, the nonce), so the browser goes back to the provider, which signs the person straight through when they are signed in there, and the callback that follows is checked like any other. The forged code is never exchanged, so this mode cannot sign a browser in as someone else: at worst it signs a person in as themselves. It works for every provider, and lands on the site root. It costs one more round trip than AllowProviderInitiated, and a provider that shows a consent or account-picker page shows it again. A provider may not be named in both (ErrProviderInitiatedConfig).
func SkipBrokenProviders ¶
func SkipBrokenProviders() Option
SkipBrokenProviders leaves out, with an Error log, a provider whose configuration fails its check or whose discovery document or key set cannot be loaded, so the other providers keep working: it disappears from the routes, the providers list and every lookup by name, and comes back when the app restarts with it fixed. With it, auth.New also loads every discovery document and key set at once (in parallel, each bounded by the HTTP client's timeout) instead of at the first flow, because that is the only moment a provider can be left out; a Fallback in use counts as loaded. auth.New still fails when every provider is broken, since there is nothing left to keep working. Without it (the default) nothing is skipped: a bad configuration fails auth.New, and an unreachable issuer fails the flow that needed it with ErrProviderFailed.
func SkipStateCookieCheck ¶
func SkipStateCookieCheck() Option
SkipStateCookieCheck accepts a callback whose state does not match the flow cookie, or that arrives with no flow cookie at all: better-auth's account.skipStateCookieCheck. The state is still required, still single use and still checked against its stored row, provider and expiry. Off by default; Init logs WarnStateCookieCheckOff at every start while it is on.
The attack it opens is login CSRF. The flow cookie is the only thing that ties a callback to the browser that started the flow; without it an attacker starts a flow, signs in at the provider as themself, stops before the callback and sends the victim that callback URL, whose state is valid in the database. The victim is then signed in to the attacker's account, and whatever they save there (a card, a document, a linked provider) the attacker reads. Its one honest use is a callback that lands where the flow cookie cannot reach (another domain, an embedded browser that drops cookies); prefer authhttp's CookieDomain or the desktop hand-off there, which keep the binding.
func StateInCookie ¶
func StateInCookie() Option
StateInCookie is better-auth's account.storeStateStrategy "cookie": a flow's state and metadata (return address, PKCE verifier, flow data) are sealed into the __Host-auth_flow cookie instead of a verification row, so a sign-in writes nothing before the callback and needs no shared store between instances. The state is still random per flow, the cookie is sealed with a key derived from auth.WithSecret (auth.KeyLabelOAuthState, which Validate requires: ErrStateCookieKey), bound to the provider, and expires with WithStateTTL; the callback clears it, so a state is used once by the browser that holds it. It cannot be combined with SkipStateCookieCheck, which drops the very cookie that holds the state.
func StoreTokens ¶
StoreTokens keeps each provider's access and refresh token sealed (AES-256-GCM), for apps that call the provider's API later; read them back with Tokens. Without it, provider tokens are never stored. With a key (32 bytes) that key seals; with none, the keys derived from auth.WithSecret under auth.KeyLabelOAuthTokens do, current secret first, so a rotated secret still opens tokens sealed under the previous one. One of the two is required (ErrBadTokenKey otherwise), and an explicit key wins.
func StoreTokensWith ¶
StoreTokensWith is StoreTokens with the tokens sealed by s instead of a key held in memory: a KMS or HSM behind the auth.Sealer interface (Vault transit, AWS KMS). It wins over auth.WithSealer and loses to a StoreTokens key. Each token is sealed with the account id as additional data, so s must refuse a value opened under another id. A seal error fails the sign-in that would have stored the tokens; an open error is reported by Tokens, never an empty token.
func TrustedProviders ¶
TrustedProviders names providers whose addresses are taken as verified even when the profile does not say so, so they may link to an existing verified identity by email. Use it only for a provider you know verifies addresses before returning them: a trusted provider that does not is an account takeover for anyone who can register that address there. The local identity must still be verified.
func UpdateProfileOnSignIn ¶
func UpdateProfileOnSignIn() Option
UpdateProfileOnSignIn copies the provider's name and image onto the user at every sign-in and link, not only when the user is created, for apps whose users expect a new avatar at the provider to show up here.
func ValidateProfile ¶
func ValidateProfile(fn ProfileHook) Option
ValidateProfile adds a ProfileHook; several run in order and the first error wins.
func VerifyEmailBeforeSignIn ¶
func VerifyEmailBeforeSignIn() Option
VerifyEmailBeforeSignIn is better-auth's requireEmailVerification for social sign-in: the callback (and an id-token or provider-initiated sign-in) creates or resolves the user as usual, but issues no session while the user has no verified email address. The browser lands on the error page (StartRequest.ErrorTo, ErrorURL or auth.WithErrorURL; the landing page when none is set) with `?error=oauth.email_not_verified`, and the result says EmailVerificationRequired. The person verifies the address the provider gave (plugin/email's resend and verify routes, which need no session) and signs in again. A profile with no email is refused with ErrEmailNotFound before anything is written, since such a user could never be released.
RequireVerifiedEmail is the stricter alternative: it refuses an unverified provider address outright and creates nothing.
func WithHTTPClient ¶
WithHTTPClient replaces the client used for provider calls, which by default times out after DefaultHTTPTimeout. A client whose CheckRedirect is nil is used as a copy that refuses redirects (ErrRedirectRefused), like the default one; an app that means its provider calls to follow redirects sets a CheckRedirect of its own.
func WithIDTokenReplayGuard ¶
func WithIDTokenReplayGuard(g IDTokenReplayGuard) Option
WithIDTokenReplayGuard replaces the default guard (a claim row in the core's verification store) with g: a shared cache, a dedicated table, a service. A nil g is refused (auth.ErrNilOption); turning the check off is AllowIDTokenReplay, never a nil.
func WithProviders ¶
WithProviders adds providers. The constructors Google and GitHub are the usual values.
func WithServerContext ¶
func WithServerContext(fn ServerContextFunc) Option
WithServerContext adds a ServerContextFunc; several run in order, and a later one's key replaces an earlier one's. Unlike StartRequest.Data, which the client chooses and the app must validate, these values come from the server and need no check at the callback.
func WithStateTTL ¶
WithStateTTL overrides DefaultStateTTL, the time a user may spend at the provider before the callback is refused as expired.
type ParamRefresher ¶
type ParamRefresher interface {
Refresher
RefreshWithParams(ctx context.Context, hc *http.Client, refreshToken auth.Token, params url.Values) (TokenSet, error)
}
ParamRefresher is a Refresher whose refresh grant takes the account's own stored parameters (TokenSet.RefreshParams). *OAuth2 implements it; the plugin calls it instead of Refresh when an account has stored parameters.
type ParamsAllowLister ¶
type ParamsAllowLister interface {
ParamsProvider
// OptionalParamList is the parameter names a request may set.
OptionalParamList() []string
}
ParamsAllowLister is a ParamsProvider that names the extra parameters a start request may set (OptionalParams). *OAuth2 and *OIDCProvider implement it; a start with StartRequest.Params at a provider that does not is refused with ErrRequestParams.
type ParamsProvider ¶
type ParamsProvider interface {
Provider
// AuthCodeURLWithParams is AuthCodeURL plus the request's parameters.
AuthCodeURLWithParams(state, codeChallenge, redirectURI string, rp RequestParams) string
// OptionalScopeList is the scopes a request may add.
OptionalScopeList() []string
}
ParamsProvider is a Provider whose authorization URL takes per-request parameters. *OAuth2 and *OIDCProvider implement it; a Provider that does not is refused a start that carries any (ErrRequestParams), rather than silently dropping what the caller asked for.
type Plugin ¶
type Plugin struct {
// contains filtered or unexported fields
}
Plugin is the OAuth sign-in method: a start and a callback operation per provider, plus accounts and unlink. Build it with New.
func New ¶
New builds the plugin and defines each provider's operations. Providers must be passed here: operations are defined once, at construction.
func (*Plugin) Accounts ¶
Accounts lists a user's linked accounts, oldest first; accounts linked in the same instant come in id order, so every engine returns one order. It takes the user id, for an app's admin pages; the accounts operation is the signed-in user's own view.
func (*Plugin) Callback ¶
func (p *Plugin) Callback(ctx context.Context, provider string, in CallbackRequest) (CallbackResult, error)
Callback finishes a flow from Go code, the typed form of GET <prefix>/oauth/<provider>/callback: in is what the provider sent back. The state must match the flow cookie of the request ctx carries, as it must over HTTP, so a callback received by the app's own handler passes that handler's request context. The session the callback issues reaches that request's response; the result carries it too (Token) for a caller with no response to write to. ErrProviderUnknown as for Start.
func (*Plugin) ImportAccount ¶
func (p *Plugin) ImportAccount(ctx context.Context, in ImportAccount) error
ImportAccount links a provider account from another system to a user, from Go only: how an import writes auth_account without touching the table by hand, so a person who signed in with Google before keeps landing on the same user. The provider must be configured (ErrProviderUnknown), the user must exist (auth.ErrUserNotFound), and the provider account must not be linked already (ErrAccountLinkedElsewhere). No tokens are stored and no hook runs: the person did not sign in. It joins the transaction in ctx when there is one.
func (*Plugin) Init ¶
Init captures the core, fills the default callback URL, hands OIDC providers the core's logger, and under SkipBrokenProviders loads every provider and leaves the broken ones out.
func (*Plugin) Operations ¶
Operations lists every start, callback, form-post, id-token and logout operation of the providers in use (SkipBrokenProviders may have left some out), then unlink, accounts, the providers list, re-authentication (and its ID-token form when a provider verifies ID tokens) and the general logout, for the transports to mount.
func (*Plugin) OptionErr ¶
OptionErr is every error the options recorded, such as auth.ErrNilOption naming an option given nil; auth.New refuses to start while it is not nil (auth.OptionChecker).
func (*Plugin) Profile ¶
Profile reads userID's current profile at provider with the stored tokens (refreshing them as Tokens does), for an app that wants the provider's latest name, picture or claims without a new sign-in. The provider must be a ProfileFetcher (ErrProfileUnsupported otherwise); a failed call is ErrProviderFailed with the reason logged. Nothing is written to the user: what to copy is the app's decision.
func (*Plugin) Providers ¶
func (p *Plugin) Providers() []ProviderInfo
Providers lists the providers in use, in configuration order, with their display names: what a sign-in page needs to draw one button per provider (each linking to /oauth/<id>/start). A provider SkipBrokenProviders left out is not listed.
func (*Plugin) RateLimits ¶
RateLimits bounds flow starts, each of which writes a state row (the re-authentication start per user too), id-token sign-ins, each of which verifies a signature, and the callbacks of providers open to provider-initiated sign-in, where a request with no state costs a token exchange.
func (*Plugin) Reauthenticate ¶
func (p *Plugin) Reauthenticate(ctx context.Context, in ReauthenticateRequest) (StartResult, error)
Reauthenticate starts a re-authentication with in.Provider for the signed-in user and returns the provider URL the browser must navigate to.
func (*Plugin) ReauthenticateIDToken ¶
func (p *Plugin) ReauthenticateIDToken(ctx context.Context, in ReauthenticateIDTokenRequest) (auth.Reauthentication, error)
ReauthenticateIDToken re-authenticates the signed-in user with a provider ID token, for a Go caller holding the request context. Without a provider that verifies ID tokens it is ErrIDTokenUnsupported.
func (*Plugin) Refresh ¶
Refresh renews userID's tokens at provider now, whatever their expiry, and stores the result: for an app that was told the token was revoked, or that wants a token good for a long job. ErrNoTokens without stored tokens, ErrCannotRefresh without a refresh token or at a provider that is not a Refresher, ErrRefreshFailed when the provider refuses.
func (*Plugin) SignInCredentials ¶
SignInCredentials counts linked accounts, for the core's last-way-in check.
func (*Plugin) SignInMethods ¶
func (p *Plugin) SignInMethods() []authhttp.SignInMethod
SignInMethods offers one button per provider in use, in configuration order, to pages that render sign-in methods (authui's sign-in and sign-up pages among them): ID "oauth.<provider>", Kind UIMethodKind, Label "Continue with <display name>", Route the provider's GET start operation. A provider SkipBrokenProviders left out, or whose start operation auth.WithDisabledOperations switched off, gets no button. Before auth.New wires the plugin there are no routes, so the list is empty.
func (*Plugin) SignsIn ¶
SignsIn reports that providers are a way in, so an app with only OAuth passes auth.New's "no sign-in method" check.
func (*Plugin) Start ¶
func (p *Plugin) Start(ctx context.Context, provider string, in StartRequest) (StartResult, error)
Start begins a sign-in, sign-up or link flow at provider from Go code, the typed form of GET <prefix>/oauth/<provider>/start: a GraphQL resolver or a handler of the app's own that wants the authorization URL. The flow's state is bound to the browser with the flow cookie on the response of the request ctx carries, so ctx must carry one (the authhttp middleware, or a contrib adapter); without it the binding is dropped and the callback answers ErrStateInvalid. The result's URL is where to send the browser; over HTTP the operation also redirects there. ErrProviderUnknown for a provider that is not configured or that SkipBrokenProviders left out.
func (*Plugin) Tables ¶
Tables declares auth_account and its alias, so auth.New can refuse a second owner of either.
func (*Plugin) Tokens ¶
Tokens returns the stored provider tokens of userID's account at provider.
An access token that expires within RefreshMargin is refreshed first when the account has a refresh token and the provider is a Refresher; the new tokens are stored. Two callers refreshing at once both succeed with a provider that keeps refresh tokens valid; with one that rotates them, the slower caller may get ErrRefreshFailed and should call again.
func (*Plugin) TypedMigrations ¶
TypedMigrations creates auth_account for engine. (provider, subject) is UNIQUE: one provider account belongs to one user.
func (*Plugin) Unlink ¶
Unlink removes a linked provider from the signed-in user. It needs a fresh sign-in, and refuses to remove the last way into the account.
func (*Plugin) UnlinkAccount ¶
UnlinkAccount is Unlink for one of several accounts of a provider.
func (*Plugin) UserTables ¶
UserTables declares that auth_account rows belong to the user in user_id, so DeleteUser removes them (no foreign key does, ADR-0002).
type PostedProfile ¶
type PostedProfile interface {
Provider
// ApplyPosted reads raw, the callback's user field, into p; "" is a
// callback that carried none.
ApplyPosted(raw string, p *Profile)
}
PostedProfile is a Provider whose form-post callback carries a user field beside the code (Apple's first-consent name): the plugin hands the field to it after the exchange. *OAuth2 implements it through PostedUser.
type Profile ¶
type Profile struct {
// Subject is the provider's stable id for the person. Never the email,
// which the person can change at the provider.
Subject string
Email string
// EmailVerified is the provider's claim that the person controls Email.
// Only a verified claim can link to an existing account.
EmailVerified bool
Name string
Image string
// Tokens are the provider's tokens, kept only with StoreTokens.
Tokens TokenSet
// Claims are the raw claims the profile was read from: the userinfo or
// API answer of a built-in FetchProfile, or the ID token's payload. A
// ValidateProfile or OnResolved hook reads provider-specific fields here
// (a Google hd, a GitHub login). Nil for a custom FetchProfile that does
// not fill it.
Claims map[string]json.RawMessage
}
Profile is what a provider says about the person who signed in.
type ProfileAction ¶
type ProfileAction string
ProfileAction is what a callback is about to do with a provider profile.
const ( // ActionSignIn: the provider account is linked; its user signs in. ActionSignIn ProfileAction = "sign_in" // ActionSignUp: nobody holds the provider account; a user is created. ActionSignUp ProfileAction = "sign_up" // ActionLink: the provider account is linked to an existing user, by a // signed-in link flow or by a verified-email match. ActionLink ProfileAction = "link" )
The three outcomes a profile can have.
type ProfileEvent ¶
type ProfileEvent struct {
Provider string
Profile Profile
Action ProfileAction
// UserID is the user the flow resolves to; "" for ActionSignUp, where
// the user does not exist yet.
UserID string
// Data is what the start request carried (StartRequest.Data); "" for an
// id-token sign-in.
Data string
// ProviderInitiated is true for a sign-in the provider started
// (AllowProviderInitiated), which no request of this app began: a hook
// may hold such a sign-in to a stricter rule.
ProviderInitiated bool
// ServerContext is what the WithServerContext functions returned at
// the start of this flow, or at the id-token call: trusted, because no
// client can set it. Nil when none ran or none returned anything.
ServerContext map[string]string
// Reauthentication is true when a signed-in user is confirming who they
// are with a linked account (EndpointReauthenticate, or
// EndpointReauthenticateIDToken). Action is then
// ActionSignIn, so a hook that holds sign-ins to a rule holds this to
// it too; refusing it leaves the session as it was.
Reauthentication bool
}
ProfileEvent is what a ValidateProfile hook sees: the provider, its profile (Claims included), what is about to happen, and whom to.
type ProfileFetcher ¶
type ProfileFetcher interface {
Provider
// FetchProfileWith reads the profile with tokens. A provider whose
// profile exists only in the ID token of the exchange (ProfileFromToken
// with no other source) answers ErrProfileUnsupported.
FetchProfileWith(ctx context.Context, hc *http.Client, tokens TokenSet) (Profile, error)
}
ProfileFetcher is a Provider that can read a profile from a token set it did not just issue: the stored tokens of a linked account. *OAuth2 and *OIDCProvider implement it; Plugin.Profile needs it.
type ProfileHook ¶
ProfileHook runs before a callback or id-token sign-in writes anything, inside its transaction. Returning an error refuses the flow: an *auth.Error is answered as it is, any other error is logged and answered as ErrProfileRefused. For allow-listing a Workspace domain, refusing a provider email, or requiring a claim.
type Provider ¶
type Provider interface {
// Name is the route segment and the stored provider name, such as
// "google". It must match auth.KindNamePattern and never change.
Name() string
// AuthCodeURL is where the browser goes to sign in. PKCE is not
// optional: every URL carries the S256 challenge.
AuthCodeURL(state, codeChallenge, redirectURI string) string
// Exchange turns the callback's code into the person's profile.
Exchange(ctx context.Context, hc *http.Client, code, codeVerifier, redirectURI string) (Profile, error)
}
Provider is one identity provider. The library ships Google and GitHub as *OAuth2 values; an app adds any other by implementing this interface.
type ProviderInfo ¶
ProviderInfo is one provider in the providers list: its id, the route segment of its start link, and its display name. Nothing secret.
type ProvidersResult ¶
type ProvidersResult struct {
Providers []ProviderInfo `json:"providers"`
}
ProvidersResult is the output of the providers route.
type ReauthenticateIDTokenRequest ¶
type ReauthenticateIDTokenRequest struct {
// Provider names the provider that issued IDToken; it must verify ID
// tokens (an IDTokenVerifier) and the user must have an account linked
// there.
Provider string `json:"provider"`
// IDToken is the provider's ID token, a compact JWS.
IDToken string `json:"idToken"`
// Nonce is the value the app sent to the provider, when it sent one,
// under the same rule as IDTokenRequest.Nonce.
Nonce string `json:"nonce,omitempty"`
// AccessToken is the access token the SDK received with the ID token,
// taken only when the ID token's at_hash binds it, and then stored under
// StoreTokens, as IDTokenRequest.AccessToken is.
AccessToken string `json:"accessToken,omitempty"`
}
ReauthenticateIDTokenRequest is the input of EndpointReauthenticateIDToken: IDTokenRequest's fields, minus SignUp (nothing is created here), plus the provider, since the operation is one route for every provider.
type ReauthenticateRequest ¶
type ReauthenticateRequest struct {
// Provider is the provider to confirm with; the user must have an
// account linked there.
Provider string `json:"provider"`
// ReturnTo is where the callback lands, with ParamReauth added: the page
// that asked, so it can rerun the refused action. Same rules as
// StartRequest.ReturnTo; empty is the site root.
ReturnTo string `json:"returnTo,omitempty"`
// ErrorTo is where a refused callback lands with `?error=<code>`; empty
// takes ReturnTo, so the user comes back to the page that asked.
ErrorTo string `json:"errorTo,omitempty"`
// Prompt is forwarded to a ParamsProvider ("login" asks the provider to
// sign the person in again rather than answer from its own session).
Prompt string `json:"prompt,omitempty"`
// LoginHint pre-fills the account at a ParamsProvider. Empty takes the
// address of the user's linked account there when there is exactly one.
LoginHint string `json:"loginHint,omitempty"`
}
ReauthenticateRequest is the input of EndpointReauthenticate.
type Refresher ¶
type Refresher interface {
Refresh(ctx context.Context, hc *http.Client, refreshToken auth.Token) (TokenSet, error)
}
Refresher is a provider that can trade a refresh token for new tokens.
type RequestAuthURLer ¶
type RequestAuthURLer interface {
Provider
AuthCodeURLForRequest(ctx context.Context, state, codeChallenge, redirectURI string, rp RequestParams) string
}
RequestAuthURLer is a Provider whose authorization URL depends on the request that starts the flow: start calls AuthCodeURLForRequest with the request's context (auth.RequestHeader and auth.RequestHost read it) in place of AuthCodeURL or AuthCodeURLWithParams. rp is empty unless the request carried parameters, which start accepts only for a ParamsProvider. plugin/oauthproxy uses it for its per-request skip header.
type RequestParams ¶
type RequestParams struct {
// Prompt is the OpenID Connect prompt value: none, login, consent,
// select_account.
Prompt string
// LoginHint pre-fills the account at the provider.
LoginHint string
// Scopes are extra scopes, already checked against OptionalScopes.
Scopes []string
// Extra are further authorization parameters, already checked against
// the provider's OptionalParams; the protocol's own keys are never in
// it.
Extra url.Values
}
RequestParams are what one start request adds to the authorization URL, on top of the provider's static configuration.
type ResolvedEvent ¶
type ResolvedEvent struct {
ProfileEvent
Created bool
Linked bool
}
ResolvedEvent is what an OnResolved hook sees: the ProfileEvent with the user the flow resolved to, and whether it created or linked.
type ResolvedHook ¶
ResolvedHook runs after a callback or id-token sign-in resolved its user, inside the same transaction and before any session is issued, with UserID set. An error fails the flow and rolls the user back. For writing provider claims into the app's own user columns (auth.SetUserColumn) or its own tables, which no DataHooks hook can do because none sees the profile.
type Revoker ¶
type Revoker interface {
Provider
// Revoke invalidates the tokens at the provider: the refresh token when
// there is one (which revokes the access tokens it issued), else the
// access token. A provider with no revocation endpoint returns nil and
// revokes nothing.
Revoke(ctx context.Context, hc *http.Client, tokens TokenSet) error
}
Revoker is a Provider that can revoke tokens it issued (RFC 7009). *OAuth2 with a RevokeURL implements it; unlink calls it for the stored tokens.
type ServerContextFunc ¶
type ServerContextFunc func(ctx context.Context, c *auth.Call, provider string) (map[string]string, error)
ServerContextFunc returns server-set values to carry across one OAuth flow, better-auth's addOAuthServerContext: a tenant id the request resolved, a plan the server chose. It runs at every start and id-token sign-in, with the provider's name; what it returns is stored with the state, where no client can set or change it, and handed back in CallbackResult.ServerContext and ProfileEvent.ServerContext. An error refuses the start: an *auth.Error is answered as it is, any other error as a server error.
type StartRequest ¶
type StartRequest struct {
// ReturnTo is a path on this site to land on after sign-in.
ReturnTo string `json:"returnTo,omitempty"`
// Link, when LinkFlag, links the provider to the signed-in user instead
// of signing in. A string because it arrives in a URL query.
Link string `json:"link,omitempty"`
// Prompt is forwarded to a ParamsProvider as the prompt parameter
// (select_account, consent, login, none).
Prompt string `json:"prompt,omitempty"`
// LoginHint is forwarded as login_hint, to pre-fill the account.
LoginHint string `json:"loginHint,omitempty"`
// Scopes are extra scopes, space-separated, each listed in the
// provider's OptionalScopes; for incremental authorization.
Scopes string `json:"scopes,omitempty"`
// Params are extra authorization parameters as a query string
// ("display=popup&ui_locales=de"), each named in the provider's
// OptionalParams (ErrBadParam otherwise): better-auth's per-request
// additionalParams. A string because it arrives in a URL query; at most
// maxFlowDataLength characters.
Params string `json:"params,omitempty"`
// Data is carried through the flow untouched and handed back in
// CallbackResult.Data: a referral code, an analytics source. At most
// maxFlowDataLength characters. It comes from the client, so the app
// treats it as untrusted input.
Data string `json:"data,omitempty"`
// ErrorTo is a path on this site a failed callback redirects to, with
// the error code in the query; empty takes the plugin's ErrorURL. Without
// either, a failed callback answers JSON.
ErrorTo string `json:"errorTo,omitempty"`
// NewUserTo is where a callback that created the user lands (an
// onboarding page) instead of ReturnTo; same rules as ReturnTo.
NewUserTo string `json:"newUserTo,omitempty"`
// SignUp, when SignUpFlag, says the person meant to sign up, which
// ExplicitSignUp requires before a user is created. A string because it
// arrives in a URL query.
SignUp string `json:"signUp,omitempty"`
}
StartRequest is the input of a start operation. It arrives in a URL query, since the browser reaches start by a link.
type StartResult ¶
type StartResult struct {
URL string `json:"url"`
}
StartResult carries the provider URL. HTTP transports redirect to it.
type TokenAuth ¶
type TokenAuth string
TokenAuth is how the client proves itself at the token endpoint (RFC 6749 section 2.3.1). Providers differ: most accept the secret in the form body, some (Reddit, PayPal, Zoom, X, Cloudflare) only as HTTP Basic, and a native or browser client has no secret at all and relies on PKCE.
const ( // ClientSecretPost sends client_id and client_secret in the form body. // The default, and what the built-in providers use. ClientSecretPost TokenAuth = "client_secret_post" // ClientSecretBasic sends them in an Authorization: Basic header, each // form-encoded first as the RFC requires. ClientSecretBasic TokenAuth = "client_secret_basic" // PublicClient sends client_id only; the code is protected by PKCE alone. // Validate refuses a ClientSecret together with it, since a secret that // is never sent is a configuration mistake. PublicClient TokenAuth = "none" // PrivateKeyJWT sends a signed assertion (RFC 7523 section 2.2) instead // of a secret: client_assertion_type and client_assertion from // OAuth2.ClientAssertion. For Entra ID workload identity and any issuer // that refuses shared secrets. Validate refuses a ClientSecret with it. PrivateKeyJWT TokenAuth = "private_key_jwt" )
type TokenSet ¶
type TokenSet struct {
AccessToken auth.Token
RefreshToken auth.Token
// ExpiresAt is when AccessToken expires; zero when the provider did not
// say.
ExpiresAt time.Time
// RefreshParams are parameters every refresh grant of this account
// must repeat, kept (sealed) with the tokens under StoreTokens: VK ID's
// device_id, taken from the callback (OAuth2.RefreshCallbackParams).
// Nil for every other provider.
RefreshParams url.Values
// IDToken is the OpenID Connect ID token the token endpoint returned,
// empty when it returned none. Stored (sealed) only with StoreTokens, for
// the id_token_hint of a provider logout.
IDToken auth.Token
// Scope is the scope the provider says it granted (RFC 6749 section 5.1),
// "" when it did not say; stored with StoreTokens so an app knows which
// scopes an incremental authorization added.
Scope string
// RefreshExpiresAt is when RefreshToken expires, for the providers that
// say (refresh_token_expires_in, refresh_expires_in); zero when unknown.
RefreshExpiresAt time.Time
// Raw is the token endpoint's whole JSON answer, for a
// FetchProfileFromTokens that needs a provider-specific field (WeChat's
// openid, Slack's authed_user). Never stored.
Raw json.RawMessage
}
TokenSet is what a provider's token endpoint issued. The tokens are auth.Token, so they print as "[redacted]" everywhere.
type UnlinkRequest ¶
type UnlinkRequest struct {
Provider string `json:"provider"`
// AccountID picks one of several accounts of Provider (a person may link
// two GitHub accounts); it must be one of the caller's own, from the
// accounts operation. Empty unlinks the oldest.
AccountID string `json:"accountId,omitempty"`
}
UnlinkRequest names a provider to unlink from the signed-in user.
type ZohoRegion ¶
type ZohoRegion string
ZohoRegion is a Zoho data centre's accounts server, which is also its OpenID Connect issuer: a Zoho app is registered in one data centre and signs in that centre's users. Each value was read from the centre's discovery document on 2026-10-08 (S256, client_secret_basic and _post, /oauth/v2/userinfo on every one).
const ( //ds:def id=zohoregions-p3z4kw8n owner=@khanakia stability=stable ZohoUS ZohoRegion = "https://accounts.zoho.com" ZohoEU ZohoRegion = "https://accounts.zoho.eu" ZohoIndia ZohoRegion = "https://accounts.zoho.in" ZohoAustralia ZohoRegion = "https://accounts.zoho.com.au" ZohoJapan ZohoRegion = "https://accounts.zoho.jp" ZohoUK ZohoRegion = "https://accounts.zoho.uk" ZohoCanada ZohoRegion = "https://accounts.zohocloud.ca" ZohoSaudi ZohoRegion = "https://accounts.zoho.sa" ZohoChina ZohoRegion = "https://accounts.zoho.com.cn" )
The Zoho data centres, better-auth-zoho's location option (with China, which it leaves out).
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package oauthtest checks an oauth.Provider against the contract the oauth plugin relies on, by driving it against a fake authorization server it starts.
|
Package oauthtest checks an oauth.Provider against the contract the oauth plugin relies on, by driving it against a fake authorization server it starts. |