zero_auth 0.3.0 copy "zero_auth: ^0.3.0" to clipboard
zero_auth: ^0.3.0 copied to clipboard

Backend-agnostic auth state machine & session lifecycle for Dart/Flutter.

Zero Auth #

English  |  简体中文

A backend-agnostic auth state machine & session lifecycle for Dart/Flutter: it models who is logged in, who they are, and how they logged in / out / recovered — a pure-Dart, headless core with zero native code, backend SDK, UI, or state-management framework.

License: MPL-2.0 Platform Flutter Dart Style: effective dart

🔔 Upgrade recommended: 0.3.0 fixes the failure paths that silently broke real apps — a proactive refresh no longer leaks an unhandled error, a dead refresh token no longer leaves you "logged in" with a stale token, and an expired persisted session now heals itself at startup. It also adds loginWith for third-party OAuth / magic links / passkeys, validAccessToken() for interceptors, and Refreshing / LoggingOut states (⚠️ breaking: exhaustive switch must handle them — prefer state.isAuthenticated). Pin zero_auth: ^0.3.0 (or git ref: release/v0.3.0).

🌐 Official Website  ·  📦 View on pub.dev  ·  🔗 View on GitHub


Table of Contents #


Features #

  • Backend-agnostic — a pure-Dart core; bring any backend by implementing AuthStrategy (REST, gRPC, Firebase, your own RPC…).
  • Explicit state machine — Unauthenticated, Authenticating, Authenticated, Refreshing, LoggingOut and AuthError, broadcast as a replay-last stream. Prefer state.isAuthenticated / state.isBusy over state is Authenticated, so a token renewal never unmounts your signed-in UI.
  • Silent restore & refresh — restores the persisted session at startup (refreshing it first when it has expired) and refreshes tokens transparently (single-flight, so concurrent callers share one call).
  • Bring your own login flow — loginWith adopts a session from any flow you drive yourself: third-party OAuth, magic links, passkeys or biometric unlock.
  • Never send an expired token — validAccessToken() renews the session first when the token has expired; ideal for HTTP interceptors.
  • Typed auth exceptions — InvalidCredentialsException, SessionExpiredException, and friends, mapped automatically from your strategy's AuthException.code.
  • Configurable refresh failure handling — refreshFailurePolicy decides whether a failed refresh signs the user out (default: yes for unrecoverable failures, no for transient ones).
  • Pluggable persistence — TokenStore is the only persistence boundary; the core ships InMemoryTokenStore, production uses a secure store (see example/).
  • Unified errors — domain failures map to AppException (from this package's error kernel); raw Exceptions never cross the public surface.
  • Network-ready — AuthTokenSource is the extension point that lets Dio / GraphQL interceptors attach Authorization: Bearer headers.
  • Zero native code — no plugins, no dart:io-only APIs; runs on server, CLI, and Flutter alike.
  • Strongly-typed session — AuthSession carries access/refresh tokens, expiry, and raw claims.
  • Session (de)serialization — AuthSession.toJson / AuthSession.fromJson make persistence a one-liner; a file-based reference store ships for server/CLI.
  • Proactive auto-refresh — pass autoRefreshAhead to AuthManager and tokens renew before expiry (single-flight), so callers rarely hit an expired access token.

Installation #

dependencies:
  zero_auth: ^0.3.0

Git #

dependencies:
  zero_auth:
    git:
      url: https://github.com/zero-labsco/zero_auth.git
      ref: release/v0.3.0   # pin the release/vX.Y.Z branch (immutable per release)

Usage #

Quick start #

import 'package:zero_auth/zero_auth.dart';

final auth = AuthManager(strategy: MyAuthStrategy());

void main() async {
  await auth.restore();        // restore a persisted session at app start
  auth.state.listen((s) {      // subscribe to state changes (replays last)
    print(s);
  });

  await auth.login(Credentials(username: 'me', password: '••••'));
}

Wire your backend (AuthStrategy) #

class MyAuthStrategy implements AuthStrategy {
  @override
  Future<AuthSession> login(Credentials c) => api.login(c.username, c.password);

  @override
  Future<AuthSession> register(RegistrationInput i) => api.register(i);

  @override
  Future<void> logout(SessionHandle h) => api.logout(h.userId);

  @override
  Future<AuthSession> refresh(RefreshToken t) => api.refresh(t.value);
}

Persist the session (TokenStore) #

The core ships only InMemoryTokenStore. For production, inject a secure store — a flutter_secure_storage-backed reference implementation lives in example/lib/secure_token_store.dart:

final auth = AuthManager(
  strategy: MyAuthStrategy(),
  tokenStore: SecureTokenStore(),   // from example/
);

Attach tokens to the network (AuthTokenSource) #

AuthManager is an AuthTokenSource. Hand it to a Dio interceptor (reference implementation in example/lib/dio_interceptor.dart):

dio.interceptors.add(AuthInterceptor(auth)); // adds `Authorization: Bearer <token>`

Example app & demo backend #

The repo ships two runnable pieces, so you can exercise the whole lifecycle end to end:

  • example/ — a Flutter app driving AuthManager (login / refresh / logout / call a protected endpoint).
  • server/ — a zero-dependency dart:io backend for the example (no pub get required).

1. Start the demo backend

cd server
dart run bin/server.dart        # listens on http://localhost:8080
Method & path Request Response
POST /login { "username": "a", "password": "b" } 200 tokens (expiresIn: 3600) · 401 invalid_credentials
POST /refresh { "refreshToken": "demo-refresh-token" } 200 new tokens · 401 invalid_refresh_token
POST /logout – 200 { "ok": true }
GET /me header Authorization: Bearer demo-access-token 200 { "userId", "displayName" } · 401 unauthorized

Any username works, but the password must be b — anything else returns 401, which is the easiest way to watch the AuthError path. CORS is enabled, so a Flutter Web build can call it directly.

2. Run the example app

cd example
flutter run
  • The app talks to the real backend by default; flip the AppBar switch to fall back to the offline fake (_DemoStrategy) when you don't want to run the server.
  • Log in with any username and password b, then press Call /me to watch AuthInterceptor attach Authorization: Bearer … and the backend echo the user back.
  • Stop the backend and log in again to see the mapped network_unreachable error instead of a raw DioException.

On an Android emulator use http://10.0.2.2:8080 instead of localhost (_baseUrl in example/lib/main.dart).

API Reference #

Type Role
AuthManager Orchestrates the state machine and session lifecycle; the main entry point.
AuthState Sealed state: Unauthenticated / Authenticating / Authenticated / AuthError.
AuthSession The active session: access/refresh tokens, expiry, display name, raw claims.
AuthStrategy Backend boundary you implement (login / register / logout / refresh).
TokenStore Persistence boundary for the active session (save / load / clear).
AuthTokenSource Read-only access-token source for network layers.
AppException The single public error type (from this package's error kernel).
Result<T> Explicit Ok / Err success-failure wrapper.

Architecture #

        login/register            success                refresh fails
   ┌──────────────┐ ┌───────────────┐ ┌──────────────────┐
   │ Unauthenticated │──▶│ Authenticating │──▶│  Authenticated   │
   └──────────────┘ └───────────────┘ └──────────────────┘
          ▲                              │   │   ▲
          │          AuthError ◀─────────┘   │   │ token near expiry
          │              │                   │   │ (single-flight refresh)
          │              └───────────────────┘   ▼
          └──────────────────────────────── logout / refresh failure ──┘

The manager holds no UI, backend, or native code. Wire your backend via AuthStrategy and your persistence via TokenStore; network layers depend only on AuthTokenSource.

Contributing #

Contributions are welcome! Please read the Contributing Guidelines before submitting issues or pull requests.

License #

Copyright (c) 2026 Zero Labs Co. (AmisKwok). This project is licensed under the Mozilla Public License 2.0 (MPL-2.0) — see the LICENSE file for details. The copyright notice and additional statements (no warranty, no endorsement) live in NOTICE.

  • Commercial use is allowed. Use, modification and closed-source distribution are permitted.
  • Modified the package? The files you modified must be published in source form under MPL-2.0. Your own app does not need to be open-sourced.
  • Used it unmodified? No source disclosure is required.
  • No endorsement. "Zero Labs Co.", "zero_auth", the logo / mascot artwork, and the author's name (AmisKwok) may not be used to endorse or promote derived products, or to imply sponsorship or affiliation, without prior written permission.
  • No warranty, no liability. The copyright holder provides no warranty and accepts no liability for any modified or derivative version; modified versions must be clearly marked as modified.

This package is provided "as is", without warranty of any kind. The author assumes no responsibility or liability for the functionality, security, or any consequences arising from the use of modified versions or derivative projects.

1
likes
0
points
349
downloads

Documentation

Documentation

Publisher

verified publisherzerolabsco.com

Weekly Downloads

Backend-agnostic auth state machine & session lifecycle for Dart/Flutter.

Repository (GitHub)
View/report issues

Topics

#authentication #jwt #token #flutter

License

unknown (license)

Dependencies

meta

More

Packages that depend on zero_auth