ghayma_auth 0.1.0 copy "ghayma_auth: ^0.1.0" to clipboard
ghayma_auth: ^0.1.0 copied to clipboard

Client for the Ghayma auth service: sign-in, sessions, 2FA, PKCE OAuth and native Google sign-in for Dart and Flutter apps.

ghayma_auth #

Dart client for the Ghayma auth service: email/password sign-in, sessions with automatic refresh, TOTP two-factor, profile management, PKCE OAuth and native Google sign-in.

Pure Dart — http and crypto only, no Flutter dependency — so the same client runs in a Flutter app, a CLI and a server. Flutter integrates through two seams: a TokenStorage adapter for persistence, and handing the OAuth redirect URI to handleRedirect.

Install #

dart pub add ghayma_auth

Quick start #

import 'package:ghayma_auth/ghayma_auth.dart';

final auth = GhaymaAuth(appSlug: 'my-app');

await auth.init(); // restore a persisted session

final result = await auth.login(
  email: 'user@example.com',
  password: 's3cret-passphrase',
);

switch (result) {
  case LoginSuccess(:final session):
    print('signed in as ${session.user.email}');
  case TwoFaRequired(:final challengeToken):
    await auth.verify2fa(challengeToken: challengeToken, code: '123456');
  case TwoFaEnrollmentRequired(:final enrollToken):
    final enrollment = await auth.enrollTotp(enrollToken: enrollToken);
    final confirmation = await auth.confirmTotp(
      code: '123456',
      enrollToken: enrollToken,
    );
    print('recovery codes: ${confirmation.recoveryCodes}');
}

final user = await auth.getUser();
await auth.logout();
auth.dispose();

register answers the same way: RegisterSuccess when tokens are issued, VerificationRequired when the app requires a verified email first.

See example/main.dart for a runnable walkthrough.

Flutter integration #

Persist the session #

The default InMemoryTokenStorage forgets everything when the process exits. Implement TokenStorage over the store of your choice — here flutter_secure_storage:

import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:ghayma_auth/ghayma_auth.dart';

class SecureTokenStorage implements TokenStorage {
  static const _key = 'ghayma_session';
  final _storage = const FlutterSecureStorage();

  @override
  Future<String?> read() => _storage.read(key: _key);

  @override
  Future<void> write(String value) => _storage.write(key: _key, value: value);

  @override
  Future<void> delete() => _storage.delete(key: _key);
}

final auth = GhaymaAuth(appSlug: 'my-app', storage: SecureTokenStorage());

Startup and routing #

Call init() once before the first frame and route on onAuthStateChange afterwards:

await auth.init();
runApp(MyApp(signedIn: auth.isAuthenticated));

auth.onAuthStateChange.listen((state) {
  switch (state.event) {
    case AuthEvent.signedIn:
      router.go('/home');
    case AuthEvent.signedOut:
      router.go('/login');
    case AuthEvent.tokenRefreshed:
    case AuthEvent.userUpdated:
      break;
  }
});

getAccessToken() refreshes when the token is within 30 s of expiry, and a background timer rotates it 60 s before it lapses, so calling it before each API request is enough.

OAuth in a browser tab #

PKCE only: no token ever travels through the OS URL handler. Open the URL with flutter_web_auth_2 5.x and hand the callback straight back:

final start = await auth.startOAuth(
  OAuthProvider.google,
  redirectUri: 'com.example.app://callback',
);

final result = await FlutterWebAuth2.authenticate(
  url: start.url,
  callbackUrlScheme: 'com.example.app',
);

await auth.handleRedirect(Uri.parse(result));

startOAuth keeps the verifier in memory and also returns it in OAuthStart.codeVerifier, so an app that hands off to an external browser can persist it and pass it back: handleRedirect(uri, codeVerifier: saved).

Native Google sign-in #

With google_sign_in 7.x the platform hands you an ID token and no browser is involved:

final account = await GoogleSignIn.instance.authenticate();
final idToken = account.authentication.idToken;

if (idToken != null) {
  await auth.signInWithIdToken(idToken: idToken);
}

Console setup #

In the Ghayma console, for your auth app:

  • Allowed Origins must list the deep link you pass as redirectUri (com.example.app://callback), exactly as written.
  • Native client IDs must list the iOS and Android OAuth client ids you use with google_sign_in; the service verifies the ID token against them.

Full walkthrough: https://docs.ghayma.cloud/guides/oauth-mobile.

Server usage #

On a Dart server, pass the app's server key (ghs_…) and forward the end user's IP so rate limits are charged to that address rather than to your server:

final auth = GhaymaAuth(appSlug: 'my-app', serverKey: Platform.environment['GHAYMA_SERVER_KEY']);

await auth.login(
  email: email,
  password: password,
  options: RequestOptions(clientIp: request.remoteAddress.address),
);

The two headers travel together or not at all: without a serverKey the clientIp is dropped, and anything that is not a bare IP literal (a whole x-forwarded-for chain, for instance) is dropped too. Never ship a server key to a client app.

Errors #

Every failure is a GhaymaAuthException with a status, a code, a message and, on rate limits, retryAfter in seconds.

code When
invalid_request the request was malformed or a field was rejected
invalid_grant a spent, expired or mismatched one-time code at exchangeCode
invalid_token a provider ID token that failed verification
rate_limited 429; read retryAfter
oauth_error the provider handed back ?error= on the redirect
network_error the request never reached the service (status 0)
timeout no answer within 30 s (status 408)
auth_error anything the service did not label — a wrong password or 2FA code is a plain 401 here, a missing session is 401 before any request
try {
  await auth.login(email: email, password: password);
} on GhaymaAuthException catch (err) {
  if (err.code == 'rate_limited') {
    print('try again in ${err.retryAfter} s');
  }
}

A refresh the service rejects clears the session and emits AuthEvent.signedOut before the error reaches you.

Contract and conformance #

Every request body, response field and error string in this package comes from the published contract, vendored at spec/auth.v1.yaml and refreshed with tool/sync_spec.sh from https://auth.ghayma.tech/openapi.yaml. CI fails when the copy is stale.

Two test layers:

dart test              # unit tests against a mock transport
tool/conformance.sh    # every method against a Prism mock of the contract

The conformance suite starts @stoplight/prism-cli on the vendored spec with --errors, so an off-contract request fails the run rather than passing silently. It needs Node 22 with npx.

Development #

dart pub get
dart format --output=none --set-exit-if-changed .
dart analyze --fatal-infos
dart test
tool/conformance.sh

License #

MIT — see LICENSE.

0
likes
160
points
86
downloads

Documentation

API reference

Publisher

verified publisherghayma.tech

Weekly Downloads

Client for the Ghayma auth service: sign-in, sessions, 2FA, PKCE OAuth and native Google sign-in for Dart and Flutter apps.

Homepage
Repository (GitHub)

License

MIT (license)

Dependencies

crypto, http

More

Packages that depend on ghayma_auth