Examples

Dart Backend Example

Use the pure-Dart authyra core outside Flutter, a minimal Shelf server with email/password auth.

authyra has zero Flutter dependency, so AuthyraClient runs the same on a backend as it does in a Flutter app. This path is architecturally sound but far less exercised in practice than the Flutter one; treat it as capable rather than battle-tested.


Setup

pubspec.yaml
dependencies:
  authyra: ^0.1.0
  shelf: ^1.4.0
  shelf_router: ^1.1.0

The client

Construct one AuthyraClient per server process (or per tenant, if you're multi-tenant, it's fully injectable, no global state to fight):

lib/auth.dart
import 'package:authyra/authyra.dart';

AuthyraClient buildAuthClient() {
  return AuthyraClient(
    providers: [
      CredentialsProvider.withTokens(
        id: 'email',
        authorize: (creds) async {
          final user = await db.users.findByEmail(creds?.email ?? '');
          if (user == null || !user.verifyPassword(creds?.password ?? '')) {
            return null; // wrong credentials
          }
          final tokens = jwtService.issue(user.id);
          return AuthSignInResult(
            user: AuthUser(id: user.id, email: user.email, name: user.name),
            accessToken: tokens.accessToken,
            refreshToken: tokens.refreshToken,
            expiresAt: tokens.expiresAt,
          );
        },
      ),
    ],
    storage: RedisAuthStorage(redisClient), // see the Custom Storage guide
    config: const AuthConfig(autoRefresh: false), // no continuously running UI loop
  );
}

autoRefresh: false is the right default for a request/response server: there's no long-lived event loop tied to one user, so refresh tokens on-demand instead (see below) rather than scheduling a background timer per session.

Routes

lib/routes.dart
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';
import 'package:authyra/authyra.dart';
import 'dart:convert';

Router buildRouter(AuthyraClient client) {
  final router = Router();

  router.post('/auth/login', (Request request) async {
    final body = jsonDecode(await request.readAsString()) as Map<String, dynamic>;
    try {
      final user = await client.signIn('email', params: CredentialsSignInParams(
        email: body['email'] as String,
        password: body['password'] as String,
      ));
      final session = await client.getSession();
      return Response.ok(jsonEncode({
        'user': {'id': user.id, 'email': user.email},
        'accessToken': session?.accessToken,
        'refreshToken': session?.refreshToken,
      }), headers: {'content-type': 'application/json'});
    } on AuthenticationFailedException {
      return Response(401, body: jsonEncode({'error': 'invalid credentials'}));
    }
  });

  router.post('/auth/refresh', (Request request) async {
    final ok = await client.refreshSession();
    if (!ok) return Response(401, body: jsonEncode({'error': 'session expired'}));
    final session = await client.getSession();
    return Response.ok(jsonEncode({'accessToken': session?.accessToken}));
  });

  router.post('/auth/logout', (Request request) async {
    await client.signOut();
    return Response.ok('{}');
  });

  return router;
}

Entry point

bin/server.dart
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf/shelf.dart';
import '../lib/auth.dart';
import '../lib/routes.dart';

Future<void> main() async {
  final client = buildAuthClient();
  await client.initialize();

  final handler = const Pipeline()
      .addMiddleware(logRequests())
      .addHandler(buildRouter(client));

  final server = await io.serve(handler, '0.0.0.0', 8080);
  print('Listening on port ${server.port}');
}

What this demonstrates

ConcernHow
No singletonAuthyraClient constructed and passed explicitly, testable in isolation
No continuous background refreshautoRefresh: false, refresh triggered by an explicit /auth/refresh route
Storage owned by youRedisAuthStorage, see Custom Storage →
Same provider contract as FlutterCredentialsProvider.withTokens is identical to the Flutter usage

See also

Copyright © 2026