Credentials Provider
CredentialsProvider handles any username/password or form-based authentication flow. It delegates credential validation to a callback you supply, you own the logic and connect it to any backend.
Package: authyra (pure Dart, no Flutter dependency)
Two constructors
Basic: user profile only
Use when your backend manages sessions server-side (cookies, opaque tokens) and only returns the user profile on login:
CredentialsProvider(
id: 'email',
authorize: (creds) async {
final res = await myApi.post('/auth/login', body: creds);
if (res.statusCode != 200) return null; // wrong credentials
return AuthUser(
id: res.data['id'],
email: res.data['email'],
name: res.data['name'],
);
},
)
The authorize callback receives the CredentialsSignInParams? passed to Authyra.instance.signIn() (as creds, read creds?.email / creds?.password) and returns an AuthUser or null. Returning null causes signIn to throw AuthenticationFailedException.
With tokens: JWT backend
Use when your backend returns access and refresh tokens in the sign-in response. Authyra stores them in the session and can refresh them silently:
CredentialsProvider.withTokens(
id: 'email',
authorize: (creds) async {
final res = await myApi.post('/auth/login', body: creds);
if (res.statusCode != 200) return null;
return AuthSignInResult(
user: AuthUser(
id: res.data['userId'],
email: res.data['email'],
name: res.data['name'],
),
accessToken: res.data['accessToken'],
refreshToken: res.data['refreshToken'],
expiresAt: DateTime.parse(res.data['expiresAt']),
);
},
)
.withTokens when your backend returns JWTs. It stores accessToken, refreshToken, and expiresAt in the session, enabling getAccessToken(), token refresh, and expiry tracking.Registration
Pass the provider to AuthyraClient:
final client = AuthyraClient(
providers: [
CredentialsProvider.withTokens(
id: 'email',
authorize: myAuthorize,
),
],
storage: SecureAuthStorage(), // or InMemoryStorage() for tests
);
The id must be unique within a client. Use it as the first argument to signIn:
await Authyra.instance.signIn('email', params: CredentialsSignInParams(
email: 'alice@example.com',
password: 's3cr3t',
));
Signing in
try {
final user = await Authyra.instance.signIn('email', params: CredentialsSignInParams(
email: 'alice@example.com',
password: 's3cr3t',
));
print('Welcome, ${user.name}');
} on AuthenticationFailedException {
// Provider returned null, wrong credentials
showError('Invalid email or password');
} on ProviderNotFoundException {
// No provider registered with id 'email'
showError('Auth not configured');
}
Multiple credential providers
Register as many CredentialsProvider instances as you need, each with a unique id:
AuthyraClient(
providers: [
CredentialsProvider(id: 'email', authorize: emailAuthorize),
CredentialsProvider(id: 'username', authorize: usernameAuthorize),
CredentialsProvider(id: 'phone', authorize: phoneAuthorize),
],
storage: storage,
)
Token refresh
CredentialsProvider sets supportsRefresh: false by default, it has no built-in refresh implementation.
To add refresh support, implement AuthProvider directly instead of using CredentialsProvider:
class MyApiProvider implements AuthProvider {
@override String get id => 'my-api';
@override bool get supportsRefresh => true;
@override
Future<AuthSignInResult?> signIn({AuthSignInParams? params}) async {
final creds = params is CredentialsSignInParams ? params : null;
final res = await myApi.post('/auth/login', body: {
'email': creds?.email,
'password': creds?.password,
});
if (res.statusCode != 200) return null;
return AuthSignInResult(
user: AuthUser(id: res.data['id'], email: res.data['email']),
accessToken: res.data['accessToken'],
refreshToken: res.data['refreshToken'],
expiresAt: DateTime.parse(res.data['expiresAt']),
);
}
@override
Future<AuthTokenResult?> refreshToken(String refreshToken) async {
final res = await myApi.post('/auth/refresh',
body: {'refreshToken': refreshToken});
if (res.statusCode != 200) return null;
return AuthTokenResult(
accessToken: res.data['accessToken'],
expiresAt: DateTime.parse(res.data['expiresAt']),
);
}
}
Testing
Use InMemoryStorage and a simple authorize callback to test without a real backend:
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);
}
return null; // wrong password
},
),
],
storage: InMemoryStorage(),
);
await client.initialize();
});
test('returns user on valid credentials', () async {
final user = await client.signIn('email', params: CredentialsSignInParams(
email: 'alice@example.com',
password: 'correct',
));
expect(user.email, 'alice@example.com');
});
test('throws on wrong password', () {
expect(
() => client.signIn('email', params: CredentialsSignInParams(
email: 'alice@example.com',
password: 'wrong',
)),
throwsA(isA<AuthenticationFailedException>()),
);
});
}