Flutter SDK reference
moth_auth is a pure-Dart core plus a thin Flutter layer. It is served
from your own instance’s pub repository, so the SDK version always tracks
the server version and nothing is fetched from pub.dev:
dependencies: moth_auth: hosted: https://auth.example.com/pub version: ^1.0.0The package is project-agnostic — endpoint and publishable key are passed at runtime. The project’s Setup tab in the admin console renders every snippet below with your real values already filled in.
The web counterpart is @moth/react — same core/UI split,
same themed screens, served from the instance’s npm registry.
There are two ways to use it: the widget layer (MothApp /
MothScope / MothLoginScreen) for a batteries-included flow, and the
client core (MothClient) for full control or non-widget code. The
widget layer is built on the client core; you can mix them.
Widget layer
Section titled “Widget layer”MothApp
Section titled “MothApp”The top-level wrapper. It owns a MothClient, restores any persisted
session on startup, and gates child behind authentication:
void main() { runApp( MothApp( config: MothConfig( endpoint: Uri.parse('https://auth.example.com'), publishableKey: 'pk_...', ), child: const MyApp(), ), );}config— aMothConfig(endpoint:, publishableKey:).http://URLs work for local development.child— shown once signed in.signedOut— the widget shown while signed out. Defaults to the built-inMothLoginScreen; pass your own to replace it.requireAuth— setfalseto always renderchildand let the app decide when to present sign-in (e.g. guest-first apps). Defaults totrue.theme— a localMothThemeoverriding the theme the server sends.billingAdapter— runs native store purchases for the paywall; passMothStoreBilling()frommoth_billing(see companions).pushAdapter— turns on push-device registration; passMothNativePush()frommoth_push. No adapter, no push — and the OS permission prompt only ever appears when your app callsMothScope.of(context).requestPushPermission().
While the session is being restored MothApp shows a neutral loading
state; it never flashes the login screen for a user who is actually
signed in.
MothScope
Section titled “MothScope”The InheritedWidget that exposes auth state to the tree.
MothScope.of(context) gives you:
state— aMothAuthState:loading,signedOut, orsignedIn(MothUser).user— the currentMothUser, ornullwhen not signed in (shorthand for thesignedIncase).client— the underlyingMothClientfor any call not surfaced as an action.- Actions:
signOut(),refreshUser(), anddeleteAccount()(which runs the re-authentication prompt the App Store requires for account deletion).
Dependents rebuild when the auth state changes:
final user = MothScope.of(context).user;return Text(user == null ? 'Signed out' : 'Signed in as ${user.email}');MothLoginScreen
Section titled “MothLoginScreen”The default sign-in surface: email/password sign-in and sign-up with
validation, forgot-password, and — when the project enables them —
Google/Apple buttons. It reads the project’s public config
(getProjectConfig) to show only the providers that are turned on, and
renders the project’s theme. No wiring required
beyond MothApp; it is what signedOut defaults to.
To use it outside MothApp, construct it directly. To restyle it, pass a
theme:; to rebuild it from parts, see theming hooks.
Client core
Section titled “Client core”MothClient is an ergonomic wrapper over the generated moth.auth.v1
gRPC stubs (native gRPC on iOS/Android, gRPC-Web on Flutter Web). Use it
directly in non-widget code, tests, or a custom UI.
final moth = MothClient(MothConfig( endpoint: Uri.parse('https://auth.example.com'), publishableKey: 'pk_...',));
await moth.restore(); // resume a persisted session, if any
final user = await moth.signIn(email: 'jane@example.com', password: '…');Methods map one-to-one to the auth API:
| Area | Methods |
|---|---|
| Session | restore(), signIn(email:, password:), signUp(email:, password:, displayName:), signOut({allDevices}) |
| Guest | signInAnonymously(), linkPassword(email:, password:), linkWithOAuth(...) — see Anonymous users |
| Current user | getMe() / refresh(), updateMe(displayName:), changePassword(current:, next:) |
requestEmailVerification(), requestEmailChange(newEmail:) |
|
| Password reset | requestPasswordReset(email:) |
| Social | signInWithOAuth(...), unlinkIdentity(provider:) |
| Account | deleteAccount(...) (fresh re-auth required) |
| Config | getProjectConfig() |
| Tokens | accessToken() |
The confirmation half of email verification, password reset, and email change is completed from the hosted pages moth emails the user — the app requests them, the link finishes them.
Auth state
Section titled “Auth state”authStateChanges is a broadcast Stream<MothAuthState> for code that
lives outside the widget tree. The state is a sealed type:
moth.authStateChanges.listen((state) { switch (state) { case MothAuthLoading(): // restoring or refreshing case MothSignedOut(): // no valid session case MothSignedIn(:final user): // user is a MothUser }});MothUser carries id, email, emailVerified, displayName,
isAnonymous (see Anonymous users), and claims — the
project-assigned custom claims, readable for client-side gating (the server
remains the authority; see custom claims).
Errors
Section titled “Errors”Every failure is a typed subclass of MothException, mapped from the
server’s gRPC status and stable ErrorInfo reason
(error model):
try { await moth.signIn(email: e, password: p);} on MothInvalidCredentials { // wrong email or password (uniform — never reveals which)} on MothEmailNotVerified { // project requires verification before sign-in} on MothRateLimited { // too many attempts; back off} on MothNetworkError { // transport failure, not an auth decision}Others include MothWeakPassword, MothEmailAlreadyExists,
MothSignUpClosed, and the guest-conversion errors
MothAnonymousDisabled / MothNotAnonymous / MothOAuthAccountExists.
Catch MothException for the catch-all.
Calling your own backend
Section titled “Calling your own backend”The reason auth exists: your API trusts the app’s requests.
moth.accessToken() always returns a valid, auto-refreshed JWT. For
package:http there is a drop-in client that attaches it:
final api = authenticatedClient(moth);final resp = await api.get(Uri.parse('https://api.example.com/todos'));Your backend verifies that token offline against the project JWKS — see verifying tokens on your backend.
The SDK does not depend on dio; add the equivalent interceptor yourself:
dio.interceptors.add(InterceptorsWrapper( onRequest: (options, handler) async { options.headers['authorization'] = 'Bearer ${await moth.accessToken()}'; handler.next(options); },));Sessions & tokens
Section titled “Sessions & tokens”- Persistence — sessions are stored in the platform keystore
(
flutter_secure_storage, i.e. Keychain / Keystore) and survive app restarts. Pass a customTokenStoretoMothClientto override — e.g.InMemoryTokenStorein tests. - Automatic refresh — access tokens refresh proactively before expiry, implemented as a gRPC interceptor with single-flight de-duplication, so concurrent callers share one refresh RPC.
- Rotation & theft detection — refresh tokens rotate on every use; a
token rejected as revoked or reused clears the stored session and emits
MothSignedOut. - Version coupling — the SDK major version matches the server major
version. The server sends
x-moth-versionresponse metadata; the SDK warns on mismatch in debug builds.
Social sign-in
Section titled “Social sign-in”moth_auth deliberately does not depend on google_sign_in or
sign_in_with_apple — you run the native provider flow yourself and hand
moth the resulting ID token. This keeps the package small and lets you
pick the plugin versions your app already uses.
// After the native Google flow yields an ID token and you generated a nonce:await moth.signInWithOAuth( provider: MothOAuthProvider.google, idToken: googleAuth.idToken!, rawNonce: nonce,);getProjectConfig() tells you which providers the project enables and the
Google client IDs to initialize them with, so the app never hardcodes
them. Apple additionally passes its one-time authorizationCode and, on
first authorization, the user’s name. Provider-specific setup lives in the
Google and Apple guides.
Anonymous users
Section titled “Anonymous users”When the project enables Allow anonymous sign-in
(getProjectConfig().anonymousEnabled), the app can start a guest
session — a real, refresh-backed account with no email or password — and
convert it to a permanent account later, keeping the same user id and all
its data. See the API contract for the server
side and backend gating.
// Start a guest — but reuse the stored session instead of calling this// every launch: each call mints a NEW guest and abandons the old one.if (moth.currentUser == null) { await moth.signInAnonymously();}
// Later, convert in place — the user id (and everything keyed on it) is kept:await moth.linkPassword(email: 'jane@example.com', password: '…');// or with a provider ID token, exactly like signInWithOAuth:await moth.linkWithOAuth( provider: MothOAuthProvider.google, idToken: googleAuth.idToken!, rawNonce: nonce,);MothUser.isAnonymous reports the state, and the access token carries an
anonymous: true claim so your backend can gate privileged actions on a
permanent account. Two things to hold onto:
- A guest is unrecoverable once its session is lost — there is no credential to sign back in with. Prompt conversion before anything the user must not lose.
- Conversion never merges accounts.
linkPassword/linkWithOAuththrowMothEmailAlreadyExistsorMothOAuthAccountExistswhen the target already belongs to someone; the guest stays as-is and your app chooses what to do (e.g. sign into the existing account instead).
Theming hooks
Section titled “Theming hooks”MothLoginScreen consumes the project’s theme
exclusively — no hardcoded styles. When the built-in screen isn’t enough:
- Override the server theme —
MothLoginScreen(theme: myTheme)orMothApp(theme: myTheme)with a localMothTheme. - Build your own screen from themed parts —
MothEmailForm,MothProviderButtons, andMothLogoare exported and pick up the same theme, so a custom layout still matches the brand. - Error-state colors are fixed under any theme — the legibility of a failure message is not themable.
Companions: native billing & push
Section titled “Companions: native billing & push”Two first-party plugin packages are served from the same /pub
repository at the same version as moth_auth; each one is a single
dependency plus one constructor argument:
moth_billing— implementsMothBillingAdapterwith StoreKit 2 on iOS and the Play Billing Library on Android.MothScope.purchaseandMothPaywallScreenrun real store purchases with zero adapter code; the server validates every receipt. See the subscriptions & paywall guide.moth_push— implementsMothPushAdapterwith APNs on iOS and Firebase Cloud Messaging on Android. While a user is signed in the SDK keeps the project’s device registry current (register on launch and token rotation, unregister on sign-out);MothScope.pushStatusandrequestPushPermission()drive your settings UI. Your backend reads the registered devices and sends the notifications itself — see the push notifications guide.
Both stay optional: moth_auth remains pure Dart, and each adapter
interface is the escape hatch for apps with their own store or push
stack.
Example app
Section titled “Example app”sdk/flutter/example/ in the repository is a runnable app against a local
moth instance, including a “Call my backend” button that hits the
example backend with an
auto-refreshed token — the full loop, app → moth → app → your API.