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}")
}