🎉 Native Google & Apple sign-in is here → read the guide
AuthenticationOverview

Authentication

Authentication is two jobs: signing users up and in, and then proving who they are on every request that follows. The supabase-auth module handles both, and every call returns a typed SupabaseResult<Session> — the same success-or- failure box used everywhere in the library (see Results & Errors).

There are several ways a user can sign in, and you can offer more than one:

  • Email & password — the classic form.
  • Phone — sign in with a phone number.
  • Magic link / OTP — the user gets a one-time code (a short-lived “one-time password”) by email or SMS instead of typing a password.
  • OAuth — “sign in with Google / GitHub / …”, where the user authenticates through another provider’s account (17 providers are supported).
  • Passkeys — passwordless sign-in backed by the device’s biometrics (see Passkeys).
  • Anonymous — a temporary session with no credentials, which you can upgrade to a real account later.

A successful sign-in gives you a session: your proof that the user is signed in. A session is a pair of tokens — an access token (sent with each request to identify the user) and a refresh token (used to get a fresh access token once the access token expires). You don’t manage those tokens by hand; the SessionManager does it for you — see Sessions.

Sign up & sign in

Create the auth client from your SupabaseClient, and create a SessionManager to hold the session that sign-in returns. After a successful call, hand the session to the manager with saveSession so it can keep the user signed in.

val auth = createAuthClient(client)
val sessionManager = createSessionManager(authClient = auth, supabaseClient = client)
 
auth.signUpWithEmail(
    email = "user@example.com",
    password = "secure-password",
).onSuccess { session ->
    sessionManager.saveSession(session)
}
 
auth.signInWithEmail(
    email = "user@example.com",
    password = "secure-password",
).onSuccess { session ->
    sessionManager.saveSession(session)
}

OAuth & PKCE

OAuth lets a user sign in through another provider’s account instead of creating a password with you. The flow is: send the user to the provider’s sign-in page, the provider redirects back to your app with a short-lived authCode, and you exchange that code for a session.

On mobile and native there’s no server to keep a secret safe, so the exchange uses PKCE — a secure handshake where your app generates a one-time codeVerifier up front and proves ownership of it when redeeming the code. generatePkceParams() creates that verifier for you; pass it back into exchangeCodeForSession.

val oauthUrl = auth.getOAuthSignInUrl(
    provider = OAuthProvider.GOOGLE,
    redirectTo = "myapp://callback",
)
 
val pkce = auth.generatePkceParams()
auth.exchangeCodeForSession(
    authCode = "code-from-callback",
    codeVerifier = pkce.codeVerifier,
)

Prefer a native account picker over a browser redirect? See Native Sign-In for Google & Apple.

Multi-factor authentication

MFA (multi-factor authentication) asks the user for a second factor on top of their password — typically a 6-digit TOTP code from an authenticator app. It’s a two-step setup: mfaEnroll registers a new factor, then mfaVerify confirms the user can produce a valid code from it. Both calls need the current access token, so the request is made on the signed-in user’s behalf.

val accessToken = sessionManager.accessToken!!
 
auth.mfaEnroll(factorType = MfaFactorType.TOTP, accessToken = accessToken).onSuccess { factor ->
    auth.mfaVerify(
        factorId = factor.id,
        challengeId = "challenge-id",
        code = "123456",
        accessToken = accessToken,
    )
}

Verifying claims locally

Every session’s access token is a JWT — a signed token that carries the user’s identity. The facts inside it (user id, email, expiry, and so on) are called claims. getClaims() decodes a JWT and verifies that it’s genuine and untampered.

For asymmetric (ES256) tokens it checks the signature on-device against your project’s JWKS — the public keys Supabase publishes for verifying tokens — so there’s no server round-trip. For anything it can’t verify locally it falls back to the Auth server.

val claims = auth.getClaims(jwt = sessionManager.accessToken!!)
claims.onSuccess { result ->
    println("Subject: ${result.claims.subject}")
}