AuthyraClient
Overview
AuthyraClient is the stateless core of Authyra. It orchestrates authentication providers and storage without holding any global state — making it fully injectable and trivially testable.
import 'package:authyra/authyra.dart';
final client = AuthyraClient(
providers: [GoogleProvider(clientId: 'YOUR_CLIENT_ID')],
storage: InMemoryStorage(),
config: const AuthConfig(),
);
You rarely interact with AuthyraClient directly in application code — AuthyraInstance (via Authyra.instance) is the app-facing singleton. Use AuthyraClient directly when writing tests or building backend services.
Constructor
AuthyraClient({
required List<AuthProvider> providers,
required AuthStorage storage,
AuthConfig config = const AuthConfig(),
})
| Parameter | Type | Description |
|---|---|---|
providers | List<AuthProvider> | One or more auth strategies. Each must have a unique id. |
storage | AuthStorage | Persistence backend. Use InMemoryAuthStorage for tests. |
config | AuthConfig | Optional tuning: token lifetime, refresh threshold, max accounts. |
The constructor validates provider IDs synchronously and throws ArgumentError on duplicates.
Methods
initialize()
Prepares the client for use. Must be called once before any other method.
Future<void> initialize()
What it does:
- Calls
storage.initialize(). - Loads and deserialises the
SessionRegistryfrom storage. - Restores the active session if one exists.
- Emits an initial
AuthStateonauthStateStream.
Throws: StorageException if the storage backend cannot be initialised.
await client.initialize();
signIn()
Authenticates a user via the named provider.
Future<AuthUser> signIn(
String providerId, {
Map<String, dynamic>? params,
})
| Parameter | Type | Description |
|---|---|---|
providerId | String | The id of the registered provider to use. |
params | Map<String, dynamic>? | Provider-specific input (credentials, OAuth code, etc.). |
Returns AuthUser — the authenticated user's profile.
Throws:
ProviderNotFoundException— no provider with thatidis registered.AuthenticationFailedException— the provider returnednull(wrong credentials) or an infrastructure error occurred.
final user = await client.signIn('email', params: {
'email': 'alice@example.com',
'password': 'secret',
});
signOut()
Signs out the currently active account.
Future<void> signOut()
Steps:
- Retrieves the active session.
- If the provider has
supportsSignOut: true, callsprovider.signOut(userId). - Removes the session from
SessionManager. - Emits
AuthState.unauthenticated()onauthStateStream.
await client.signOut();
refreshSession()
Silently refreshes the active session's access token.
Future<bool> refreshSession()
Returns true if the token was refreshed successfully, false if the refresh token is invalid or the provider does not support refresh.
Called automatically by AuthyraInstance — you rarely need to call this directly.
final refreshed = await client.refreshSession();
if (!refreshed) {
// Token is invalid — the session has been cleared.
}
getSession()
Returns the active AuthSession, or null if no user is signed in.
Future<AuthSession?> getSession()
final session = await client.getSession();
print(session?.accessToken);
getUser()
Returns the active AuthUser, or null if not authenticated.
Future<AuthUser?> getUser()
registerProvider()
Dynamically adds a provider after construction.
void registerProvider(AuthProvider provider)
Useful for providers that require runtime configuration (e.g., a tenant-specific OAuth client ID):
client.registerProvider(
OAuth2Provider(config: tenantConfig),
);
Throws ArgumentError if a provider with the same id is already registered.
Properties
authStateStream
Broadcast stream of AuthState changes. Deduplicated — identical consecutive states are not re-emitted.
Stream<AuthState> get authStateStream
client.authStateStream.listen((state) {
if (state.isAuthenticated) {
print('Signed in as ${state.user!.email}');
}
});
sessionStream
Broadcast stream of the raw AuthSession, or null on sign-out.
Stream<AuthSession?> get sessionStream
accounts
Lazy accessor for the AccountManager. Manages the multi-account session registry.
AccountManager get accounts
await client.accounts.getAll(); // List<AuthUser>
await client.accounts.switchTo(id); // activate a different session
await client.accounts.signOut(id); // sign out one account
await client.accounts.signOutAll(); // clear all sessions
isInitialized
true after initialize() has completed successfully.
bool get isInitialized
AuthConfig
Fine-tune the client's session behaviour:
const AuthConfig({
int tokenLifetime = 3600, // seconds before access token expires
int refreshBeforeExpiry = 300, // seconds before expiry to trigger refresh
int maxAccounts = 5, // maximum concurrent signed-in accounts
bool autoRefresh = true, // refresh token automatically when expired
})
final client = AuthyraClient(
providers: [...],
storage: storage,
config: const AuthConfig(
tokenLifetime: 7200, // 2 hours
refreshBeforeExpiry: 600, // refresh 10 minutes early
maxAccounts: 3,
),
);
Testing
Prefer AuthyraClient directly in unit tests — no singleton to reset between tests:
import 'package:test/test.dart';
import 'package:authyra/authyra.dart';
void main() {
late AuthyraClient client;
setUp(() async {
client = AuthyraClient(
providers: [
CredentialsProvider(
id: 'email',
authorize: (creds) async {
if (creds?['password'] == 'correct') {
return AuthUser(id: '1', email: creds!['email'] as String);
}
return null;
},
),
],
storage: InMemoryAuthStorage(),
);
await client.initialize();
});
test('returns user on valid credentials', () async {
final user = await client.signIn('email', params: {
'email': 'alice@example.com',
'password': 'correct',
});
expect(user.email, 'alice@example.com');
});
test('throws on invalid credentials', () async {
expect(
() => client.signIn('email', params: {'email': 'a@b.com', 'password': 'wrong'}),
throwsA(isA<AuthenticationFailedException>()),
);
});
test('isAuthenticated after sign in', () async {
await client.signIn('email', params: {'email': 'a@b.com', 'password': 'correct'});
final session = await client.getSession();
expect(session, isNotNull);
});
}