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

Framework-agnostic authentication runtime capabilities for server ecosystems.

server_auth #

Framework-agnostic authentication runtime primitives and provider implementations.

Includes built-in providers for Google, Discord, Microsoft Entra, Apple, Twitter/X, Facebook, GitLab, Slack, Spotify, LinkedIn, Twitch, Dropbox, and Telegram.

server_auth is designed to be consumed by framework adapters. It provides auth building blocks (providers, JWT, CSRF, gates/authorization, callbacks, token utilities) without requiring Routed-specific runtime types.

Installation #

dependencies:
  server_auth: ^0.1.0

Entry points #

  • package:server_auth/server_auth.dart (umbrella export)

Use the package umbrella library for the public API.

Package Selection #

  • Use server_auth for auth runtime primitives and provider implementations.
  • Use server_contracts for contract-only abstractions.
  • Use adapter packages (routed, shelf_auth, etc.) for framework-specific HTTP/session wiring.

Quick start #

import 'package:server_auth/server_auth.dart';

final google = googleProvider(
  GoogleProviderOptions(
    clientId: 'google-client-id',
    clientSecret: 'google-client-secret',
    redirectUri: 'https://example.com/auth/callback/google',
  ),
);

final providers = <AuthProvider>[google];

Use providers with your framework adapter to wire callback routes, session handling, and auth lifecycle.

For Routed applications, use routed_auth: initialize AuthServiceProvider alongside Engine.defaultProviders, then add the adapter's auth middleware and routes. server_auth itself never registers framework providers.

Using with Shelf #

Use shelf_auth for Shelf-specific middleware while keeping providers and auth contracts in server_auth.

dependencies:
  server_auth: ^0.1.0
  shelf_auth: ^0.1.0
import 'package:server_auth/server_auth.dart';
import 'package:shelf_auth/shelf_auth.dart';

final providers = <AuthProvider>[
  const AuthProvider(
    id: 'credentials',
    name: 'Credentials',
    type: AuthProviderType.credentials,
  ),
];

final middleware = authProvidersEndpoint(providers: providers);

JWT issue + verify example #

import 'package:server_auth/server_auth.dart';

final options = const JwtSessionOptions(
  secret: 'replace-with-a-strong-secret',
  issuer: 'example-app',
  audience: <String>['example-api'],
  maxAge: Duration(minutes: 30),
);

final issued = issueAuthJwtToken(
  options: options,
  claims: <String, dynamic>{'sub': 'user_42', 'roles': <String>['admin']},
);

final verifier = JwtVerifier(options: options.toVerifierOptions());
final payload = await verifier.verifyToken(issued.token);
print(payload.subject);

final refreshed = await refreshAuthJwtTokenIfNeeded(
  options: options,
  claims: payload.claims,
  updateAge: const Duration(minutes: 15),
  resolveClaims: (claims) => claims,
);

final verifiedSession = await verifyAuthJwtSessionToken(
  token: issued.token,
  options: options,
);
print(verifiedSession?.user.id);

final resolvedSession = await resolveAuthJwtSessionWithRefresh(
  token: issued.token,
  options: options,
  updateAge: const Duration(minutes: 15),
  resolveClaims: (claims, user) => claims,
);
print(resolvedSession?.refreshCookie != null); // refreshed or not

Bearer validation helpers for adapters #

Use verifyJwtBearerAuthorizationAndWriteAttributes in framework middleware to share JWT bearer auth orchestration (token extraction, verification, request attribute writes, and optional callback):

final result = await verifyJwtBearerAuthorizationAndWriteAttributes<MyContext>(
  authorizationHeader: request.header('authorization'),
  verifier: jwtVerifier,
  setAttribute: request.setAttribute,
  context: context,
  onVerified: (payload, ctx) async {
    if (payload.claims['scope'] == null) {
      throw JwtAuthException('missing_scope');
    }
  },
);

print(result.payload.subject);

For OAuth introspection middleware, use validateOAuthBearerAuthorizationAndWriteAttributes:

final validation =
    await validateOAuthBearerAuthorizationAndWriteAttributes<MyContext>(
      authorizationHeader: request.header('authorization'),
      introspector: oauthIntrospector,
      setAttribute: request.setAttribute,
      context: context,
      onValidated: (result, ctx) async {
        if (result.scope?.contains('api:read') != true) {
          throw OAuth2Exception('insufficient_scope');
        }
      },
    );

print(validation.result.raw);

Email verification orchestration helpers #

Use startAuthEmailSignIn to share the email verification start flow:

final payload = await startAuthEmailSignIn<MyContext>(
  adapter: adapter,
  tokenStore: tokenStore,
  provider: emailProvider,
  context: context,
  email: 'user@example.com',
  callbackUrl: '/dashboard',
  sessionStrategy: AuthSessionStrategy.session,
  callbackKey: '_auth.callback',
  writeSession: (key, value) => sessionStore[key] = value,
);

print(payload.pendingResult.session.strategy);

Use resolveAuthEmailVerificationSignIn in callback handlers:

final resolved = await resolveAuthEmailVerificationSignIn(
  adapter: adapter,
  tokenStore: tokenStore,
  email: email,
  token: token,
  callbackKey: '_auth.callback',
  readSession: (key) => sessionStore[key],
);

Credentials orchestration helpers #

Use requireAuthorizedCredentialsSignIn and requireAuthorizedCredentialsRegistration to perform provider/adapter credential resolution with standard auth errors:

final user = await requireAuthorizedCredentialsSignIn(
  adapter: adapter,
  provider: credentialsProvider,
  context: context,
  credentials: credentials,
);

OAuth callback orchestration helper #

Use resolveOAuthSignInForProvider in framework adapters to share OAuth callback exchange/profile/user resolution logic without depending on Routed.

final resolved = await resolveOAuthSignInForProvider<MyContext, Map<String, dynamic>>(
  adapter: adapter,
  context: context,
  provider: oauthProvider,
  code: authorizationCode,
  codeVerifier: pkceVerifier,
  httpClient: httpClient,
);

await adapter.linkAccount(resolved.account);
print(resolved.user.id);
print(resolved.profile);

Use resolveOAuthAuthorizationStart to share OAuth begin-flow state/PKCE/callback session persistence:

final start = await resolveOAuthAuthorizationStart<MyContext, Map<String, dynamic>>(
  context: context,
  provider: oauthProvider,
  stateKey: '_auth.state',
  pkceKey: '_auth.pkce',
  callbackKey: '_auth.callback',
  callbackUrl: '/dashboard',
  writeSession: (key, value) => sessionStore[key] = value,
);

return start.authorizationUri;

For callback handlers, resolveOAuthCallbackSessionValues reads state/verifier/ callback URL using the same key contract:

final values = resolveOAuthCallbackSessionValues(
  providerId: oauthProvider.id,
  stateKey: '_auth.state',
  pkceKey: '_auth.pkce',
  callbackKey: '_auth.callback',
  readSession: (key) => sessionStore[key],
);

Or use the higher-level callback helper to validate state, resolve sign-in, and persist linked accounts in one call:

final callback = await resolveOAuthCallbackSignInForProvider<MyContext, Map<String, dynamic>>(
  adapter: adapter,
  context: context,
  provider: oauthProvider,
  code: authorizationCode,
  receivedState: stateFromRequest,
  stateKey: '_auth.state',
  pkceKey: '_auth.pkce',
  callbackKey: '_auth.callback',
  readSession: (key) => sessionStore[key],
  httpClient: httpClient,
);

print(callback.signIn.user.id);
print(callback.callbackUrl);

Adapter attribute mapping helpers #

When writing framework adapters, use these helpers to store verified auth payloads with consistent attribute keys:

import 'package:server_auth/server_auth.dart';

final attributes = <String, Object?>{};

writeJwtPayloadAttributes(
  payload,
  setAttribute: (key, value) => attributes[key] = value,
);

writeOAuthValidationAttributes(
  oauthValidation,
  setAttribute: (key, value) => attributes[key] = value,
);

final callbackUrl = resolveAndSanitizeRedirectCandidate(
  payload,
  queryParameters,
  requestUri: requestUri,
  fallbackHost: 'app.test',
  fallbackScheme: 'https',
);

final callbackFromResolver = await resolveAndSanitizeRedirectWithResolver(
  payload,
  queryParameters,
  requestUri: requestUri,
  fallbackHost: 'app.test',
  fallbackScheme: 'https',
  resolveRedirect: (candidate) async => candidate,
);

Callback helper for redirect fallbacks #

Use resolveAuthRedirectTargetWithFallback when your adapter supports redirect callbacks but should preserve a framework-provided fallback URL.

final resolvedUrl = await resolveAuthRedirectTargetWithFallback<MyContext>(
  callback: callbacks.redirect,
  context: AuthRedirectCallbackContext<MyContext>(
    context: context,
    url: '/requested',
    baseUrl: 'https://app.test',
  ),
  fallbackUrl: '/requested',
);

When you already have an AuthCallbacks<TContext> instance, use the compact wrapper:

final resolvedFromCallbacks = await resolveAuthRedirectWithCallbacks<MyContext>(
  callbacks: callbacks,
  context: context,
  url: '/requested',
  baseUrl: 'https://app.test',
);

The same pattern exists for JWT/session callback orchestration:

final signInRedirect = await resolveAuthSignInRedirectWithCallbacks<MyContext>(
  callbacks: callbacks,
  context: context,
  user: user,
  strategy: AuthSessionStrategy.session,
  callbackUrl: '/requested',
);

// Throws AuthFlowException('sign_in_blocked') if callbacks.signIn denies.

final finalRedirect = await resolveAuthSignInRedirectTarget<MyContext>(
  callbacks: callbacks,
  context: context,
  user: user,
  strategy: AuthSessionStrategy.session,
  callbackUrl: '/requested',
  resolveRedirect: (candidate) => adapterResolveRedirect(candidate),
);

final claims = await resolveAuthJwtClaimsWithCallbacks<MyContext>(
  callbacks: callbacks,
  context: context,
  user: user,
  strategy: AuthSessionStrategy.jwt,
);

final sessionPayload = await resolveAuthSessionPayloadWithCallbacks<MyContext>(
  callbacks: callbacks,
  context: context,
  session: session,
  strategy: AuthSessionStrategy.session,
);

final jwtIssue = await issueAuthJwtSessionWithCallbacks<MyContext>(
  callbacks: callbacks,
  context: context,
  options: jwtOptions,
  user: user,
  strategy: AuthSessionStrategy.jwt,
);

final signInResult = await resolveAuthSignInResultForStrategyWithCallbacks<MyContext>(
  callbacks: callbacks,
  context: context,
  strategy: AuthSessionStrategy.jwt,
  user: user,
  redirectUrl: finalRedirect,
  jwtOptions: jwtOptions,
);

final authResult = signInResult.result;
final issuedJwtCookie = signInResult.issuedJwt?.cookie;

Authorization and gates example #

import 'package:server_auth/server_auth.dart';

final gates = AuthGateService<Map<String, dynamic>>();
gates.register('posts.update', rolesGate(<String>['editor', 'admin'], any: true));

final principal = AuthPrincipal(id: 'user_42', roles: <String>['admin']);
final allowed = await gates.can(
  'posts.update',
  context: <String, dynamic>{'resourceId': 'post_1'},
  principal: principal,
);
print(allowed); // true

// Managed gate registration that preserves unmanaged entries:
final managed = <String>{};
final registered = registerGateCallbacksSafely<Map<String, dynamic>>(
  gates.registry,
  <String, AuthGateCallback<Map<String, dynamic>>>{
    'posts.publish': rolesGate(<String>['editor']),
  },
  managed: managed,
);
managed
  ..clear()
  ..addAll(registered);

Framework Adapter Session Runtime #

RememberSessionAuthRuntime<TContext> provides framework-agnostic remember-me/session principal logic. Frameworks supply an AuthSessionRuntimeAdapter<TContext> to map request/session/cookie behavior.

import 'package:server_auth/server_auth.dart';

final runtime = RememberSessionAuthRuntime<MyContext>(
  adapter: myAdapter,
  rememberCookieName: 'remember_token',
  defaultRememberDuration: const Duration(days: 30),
  sessionPrincipalKey: '__auth.principal',
);

await runtime.login(
  context,
  AuthPrincipal(id: 'user-1', roles: const <String>['user']),
  rememberMe: true,
);

await runtime.hydrate(context); // restore/rotate remember token when needed
await runtime.logout(context);

For adapters that keep issued-at metadata, use syncAuthSessionRefresh to apply initialize/refresh/keep behavior:

syncAuthSessionRefresh(
  issuedAtValue: session['__auth.session.issued_at'] as String?,
  updateAge: const Duration(minutes: 5),
  writeIssuedAt: (value) {
    session['__auth.session.issued_at'] = serializeAuthSessionIssuedAt(value);
  },
  touchSession: () => sessionTouch(),
);

Minimal adapter skeleton #

Use a small framework-specific adapter that maps your persistence layer into server_auth contracts:

class MyAuthAdapter extends AuthAdapter {
  final Map<String, AuthUser> usersById = <String, AuthUser>{};

  @override
  FutureOr<AuthUser?> getUserById(String id) {
    return usersById[id];
  }

  @override
  FutureOr<AuthUser> createUser(AuthUser user) {
    usersById[user.id] = user;
    return user;
  }

  @override
  FutureOr<AuthSession?> getSession(String sessionToken) {
    // Query your DB or cache here.
    return null;
  }
}

Keep adapters focused on boundary mapping and keep provider/JWT/gate logic in server_auth.

Typed Profiles #

Every OAuth provider includes a typed profile model and serializer/parsers, so user info mapping can stay type-safe.

Telegram (Non-OAuth) #

Telegram uses widget-based auth with HMAC verification via telegramProvider.

Runnable example #

dart run example/main.dart

See example/main.dart for provider registration, JWT flows, and gate checks. See example/README.md for run instructions and expected output.

Migration Notes #

If older code imported provider factories or auth primitives from Routed entrypoints, switch to direct server_auth imports to keep auth logic reusable across frameworks.

Validation #

dart analyze
dart test
dart run example/main.dart

License #

MIT

0
likes
0
points
685
downloads

Publisher

unverified uploader

Weekly Downloads

Framework-agnostic authentication runtime capabilities for server ecosystems.

Repository (GitHub)
View/report issues

Funding

Consider supporting this project:

www.buymeacoffee.com

License

unknown (license)

Dependencies

crypto, http, jose, server_contracts

More

Packages that depend on server_auth