Providers

Credentials Provider

Email/password and any form-based authentication with CredentialsProvider.

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 params map passed to Authyra.instance.signIn() 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']),
    );
  },
)
Always use .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: {
  'email':    'alice@example.com',
  'password': 's3cr3t',
});

Signing in

try {
  final user = await Authyra.instance.signIn('email', params: {
    '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 AuthProviderType get type => AuthProviderType.credentials;
  @override bool get supportsRefresh => true;

  @override
  Future<AuthSignInResult?> signIn({Map<String, dynamic>? params}) async {
    final res = await myApi.post('/auth/login', body: params);
    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'] as String);
            }
            return null; // wrong password
          },
        ),
      ],
      storage: InMemoryStorage(),
    );
    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 wrong password', () {
    expect(
      () => client.signIn('email', params: {
        'email':    'alice@example.com',
        'password': 'wrong',
      }),
      throwsA(isA<AuthenticationFailedException>()),
    );
  });
}

See also

Copyright © 2026