Core Concepts

Storage

The AuthStorage interface — pluggable session persistence, security guidance, and implementing a custom backend.

AuthStorage is the single persistence interface used by Authyra's SessionManager. The core package ships no concrete implementation by design — you supply a backend suited to your runtime.


Interface

abstract class AuthStorage {
  Future<void>         initialize();
  Future<String?>      read(String key);
  Future<void>         write(String key, String value);
  Future<bool>         delete(String key);
  Future<void>         clear();
  Future<bool>         containsKey(String key);
  Future<List<String>> getKeysWithPrefix(String prefix);
}
MethodDescription
initialize()One-time setup — key derivation, DB connection, schema migration. Must be idempotent.
read(key)Return the stored string or null if absent
write(key, value)Persist value under key, overwriting any existing entry. Must survive process restarts.
delete(key)Remove key. Return true if it existed, false if absent. Never throws for missing keys.
clear()Delete all entries — irreversible. Call only during full sign-out-all or factory reset.
containsKey(key)true if key is present
getKeysWithPrefix(prefix)Enumerate all keys starting with prefix — used for multi-account session discovery

Choosing a backend

RuntimeRecommended
Flutter mobileSecureAuthStorage from authyra_flutter (Keychain on iOS, Keystore on Android)
Flutter webSecureAuthStorage from authyra_flutter (Web Crypto encrypted localStorage)
Tests / devInMemoryStorage from authyra (no persistence — resets between test cases)
Dart CLIEncrypted file or OS keyring
Backend / ShelfRedis, encrypted DB column, or a secrets manager
Authyra serialises access tokens and refresh tokens into storage. Never use plaintext backends — SharedPreferences, plain localStorage, or unencrypted files. Always use an encrypted store in production.

Flutter — SecureAuthStorage

SecureAuthStorage wraps flutter_secure_storage and is the ready-to-use implementation for Flutter apps. Import it from authyra_flutter:

import 'package:authyra_flutter/authyra_flutter.dart';

await Authyra.initialize(
  client: AuthyraClient(
    providers: [...],
    storage: SecureAuthStorage(),
  ),
);

No configuration is needed. flutter_secure_storage selects the platform-native encrypted store automatically:

  • iOS / macOS → Keychain
  • Android → EncryptedSharedPreferences (Android Keystore)
  • WeblocalStorage with Web Crypto AES-GCM encryption

Tests — InMemoryStorage

InMemoryStorage stores everything in a Map<String, String> — fast, portable, and resets between test cases automatically:

import 'package:test/test.dart';
import 'package:authyra/authyra.dart';

void main() {
  late AuthyraClient client;

  setUp(() async {
    client = AuthyraClient(
      providers: [myProvider],
      storage: InMemoryStorage(),  // fresh instance per test
    );
    await client.initialize();
  });

  test('session is stored after sign-in', () async {
    await client.signIn('email', params: {
      'email': 'alice@example.com', 'password': 'secret',
    });
    final token = await client.getAccessToken();
    expect(token, isNotNull);
  });
}

Never use InMemoryStorage in production — sessions are lost on process restart.


Implementing a custom backend

Implement AuthStorage to connect any storage system. Below is a Redis example for a Dart backend:

import 'package:authyra/authyra.dart';
import 'package:resp_client/resp_client.dart';

class RedisAuthStorage implements AuthStorage {
  final RespClient _client;
  final String _namespace;

  RedisAuthStorage(this._client, {String namespace = 'authyra'})
      : _namespace = namespace;

  String _k(String key) => '$_namespace:$key';

  @override
  Future<void> initialize() async {
    // Verify the Redis connection is alive
    await _client.command(['PING']);
  }

  @override
  Future<String?> read(String key) async {
    final result = await _client.command(['GET', _k(key)]);
    return result.payload as String?;
  }

  @override
  Future<void> write(String key, String value) async {
    await _client.command(['SET', _k(key), value]);
  }

  @override
  Future<bool> delete(String key) async {
    final deleted = await _client.command(['DEL', _k(key)]);
    return (deleted.payload as int) > 0;
  }

  @override
  Future<void> clear() async {
    final keys = await getKeysWithPrefix('');
    if (keys.isNotEmpty) {
      await _client.command(['DEL', ...keys.map(_k)]);
    }
  }

  @override
  Future<bool> containsKey(String key) async {
    final result = await _client.command(['EXISTS', _k(key)]);
    return (result.payload as int) > 0;
  }

  @override
  Future<List<String>> getKeysWithPrefix(String prefix) async {
    final result = await _client.command(['KEYS', '${_k(prefix)}*']);
    final fullKeys = (result.payload as List).cast<String>();
    // Strip the namespace prefix before returning
    return fullKeys
        .map((k) => k.substring('$_namespace:'.length))
        .toList();
  }
}

Pass it to AuthyraClient:

final client = AuthyraClient(
  providers: [myProvider],
  storage: RedisAuthStorage(redisClient),
);
await client.initialize();

Key structure

SessionManager stores all session data under a single registry key by default:

authyra:session_registry  →  { JSON-encoded SessionRegistry }

getKeysWithPrefix is used by multi-account flows to enumerate sessions. When implementing a custom backend, ensure getKeysWithPrefix('') (empty prefix) returns all keys managed by this storage instance so that clear() can remove them all.


Security checklist

  • Storage backend uses OS-level encryption (Keychain, Keystore, Web Crypto, Redis at-rest encryption)
  • clear() is called on sign-out-all and account deletion flows
  • initialize() is awaited before any other storage operation
  • write is durable — survives process restarts and power events
  • write is atomic — no partial writes that could corrupt session data

See also

Copyright © 2026