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
| Concern | How |
|---|---|
| No singleton | AuthyraClient constructed and passed explicitly, testable in isolation |
| No continuous background refresh | autoRefresh: false, refresh triggered by an explicit /auth/refresh route |
| Storage owned by you | RedisAuthStorage, see Custom Storage → |
| Same provider contract as Flutter | CredentialsProvider.withTokens is identical to the Flutter usage |