Authentication
Every sign-in method resolves to an AuthState — it never throws across the public surface.
Check the returned (or streamed) state rather than wrapping calls in try/catch. See
Errors for the one exception (programmer errors).
All of the flows below operate on the RakomiAuth instance for the enclosing RakomiProvider,
reached via RakomiProvider.of(context) (throws if there is no enclosing provider) or
RakomiProvider.maybeOf(context) (nullable).
Password sign-in and sign-up
Section titled “Password sign-in and sign-up”final auth = RakomiProvider.of(context);
final state = await auth.signIn(email: email, password: password);// orfinal state = await auth.signUp(email: email, password: password);Both resolve to AuthState.authenticated(user, session) on success. A primary-login response
that requires a second factor resolves to AuthState.error(SdkError(code: mfaStepUpRequired, …))
instead — see MFA (TOTP) step-up below.
Magic link and email OTP
Section titled “Magic link and email OTP”await auth.signInWithMagicLink(email); // sends a sign-in link to `email`await auth.signInWithEmailOtp(email); // sends a one-time code to `email`Both requests only trigger the send — they do not themselves resolve to an authenticated state. The user completes the flow by following the emailed link or submitting the code through your own UI against the API’s canonical passwordless-completion endpoint.
Social / OAuth sign-in
Section titled “Social / OAuth sign-in”Use the pre-built RakomiSignInButton / RakomiSignInForm widgets, or drive the flow
programmatically with the top-level performSocialSignIn function:
import 'package:rakomi_flutter/rakomi_flutter.dart';
final state = await performSocialSignIn( auth, SocialProvider.google, redirectUri: 'myapp://callback',);RakomiAuth.signInWithProvider(provider) (the method on the controller itself) is intentionally
unimplemented — it throws UnsupportedError, because the OAuth flow needs a redirectUri
that the controller does not own. Always go through performSocialSignIn, or through
RakomiSignInButton / RakomiSignInForm, which read redirectUri from the enclosing
RakomiProvider.
Supported providers (SocialProvider enum): google, github, microsoft, apple,
discord, facebook, slack, twitter, gitlab, linkedin. An eudiWallet value is
reserved as a forward-compatibility slot for the EU Digital Identity Wallet mandate and is not
wired to a live flow today — switch statements over SocialProvider should stay
non-exhaustive (use a default: case) so this and future additive values do not break your
build.
The flow itself is standard Authorization Code + PKCE (RFC 8252): the SDK opens the system
browser (never an in-app WebView), generates and persists a PKCE verifier/challenge and a
CSRF state value, and on the redirect callback validates state (constant-time), the
optional iss parameter (RFC 9700 §4.4 mix-up defense against the configured issuer), then
exchanges the authorization code for tokens.
Redirect-URI scheme rules (enforced by performSocialSignIn before any network call):
- On iOS/Android:
redirectUrimust use a custom URL scheme.http,https,javascript,data,file,vbscript,aboutare all rejected with anArgumentError. - On Flutter Web:
https://is accepted (popuppostMessageflow). The always-banned scheme list above still applies.
MFA (TOTP) step-up
Section titled “MFA (TOTP) step-up”When a primary sign-in response carries next_action: "verify_mfa", signIn/signUp resolve
to AuthState.error(SdkError(code: SdkErrorCode.mfaStepUpRequired, errorMessage: challengeId)).
Complete the challenge with the 6-digit TOTP code:
final state = await auth.verifyMfaImpl(code, challengeId: challengeId);RakomiAuth.verifyMfa(code) (without a challengeId) is, like signInWithProvider,
intentionally unimplemented — it throws UnsupportedError pointing you at verifyMfaImpl
(the extension method) or the pre-built RakomiSignInForm, which already wires this transition.
The code is validated client-side against ^\d{6}$ before any network call. A user with no
TOTP factor configured gets back SdkErrorCode.mfaStepUpUnavailable.
Sign-out
Section titled “Sign-out”await auth.signOut();signOut() revokes the refresh token server-side (best-effort), then clears all locally
persisted state for the current tenant — the refresh token, cached user snapshot, and the
tenant’s JWKS cache — and emits AuthEventType.signedOut. Storage keys are tenant-namespaced,
so signing out of one RakomiProvider does not disturb a sibling provider for a different
tenant in the same widget tree.
Biometric unlock
Section titled “Biometric unlock”final unlocked = await auth.unlockWithBiometric(reason: 'Unlock Rakomi');Triggers the platform biometric prompt via the native adapter and, on success, re-runs session
hydration. Returns true iff the resulting state is AuthStateAuthenticated. A failure (user
cancellation, lockout, no enrolled biometric, or hardware unavailable) resolves to
AuthState.error(SdkError(code: SdkErrorCode.biometricError, …)) with a reason identifying
which case occurred — see Errors.
Pre-built widgets
Section titled “Pre-built widgets”RakomiSignInForm— password + passwordless + social + MFA-TOTP in one widget, Material on Android/Web and Cupertino on iOS. Password and TOTP fields ship with input hardening (obscureText, disabled interactive selection/autocorrect/suggestions,autofillHints).RakomiSignInButton— a single one-tap social sign-in CTA for oneSocialProvider.RakomiAuthGate— conditional renderer keyed offauthStateChanges; supplysignedInandsignedOutbuilders, with optionalloadinganderrorbuilders.RakomiUserAvatar— avatar + tooltip (email, session expiry) with a built-in sign-out menu.
Reacting to state and lifecycle events
Section titled “Reacting to state and lifecycle events”auth.authStateChanges.listen((state) { state.when( loading: () { /* ... */ }, unauthenticated: () { /* ... */ }, authenticated: (user, session) { /* ... */ }, error: (error) { /* ... */ }, );});
auth.events.listen((event) { // AuthEvent(type, severity, timestamp, ...) — an append-only lifecycle log, // distinct from the current-snapshot `authStateChanges` stream.});events is a consumer-pull-only stream — the SDK does not forward events to any sink on its
own. See Session & tokens for the audit-logging
guidance.