dartapi_auth

JWT authentication and API key middleware for the DartAPI ecosystem. Supports HS256 (symmetric) and RS256 (asymmetric) signing, token revocation, and static API key validation.


Installation

dependencies:
  dartapi_auth: ^0.0.6

JwtService (HS256)

Use HS256 for single-service deployments where the signing and verification key can stay on one server:

final jwtService = JwtService(
  accessTokenSecret: 'my-access-secret',
  refreshTokenSecret: 'my-refresh-secret',
  issuer: 'my-app',
  audience: 'api-clients',
);

JwtService (RS256)

Use RS256 when multiple services need to verify tokens without sharing the signing key. Distribute the public key freely; keep the private key server-side only.

final jwtService = JwtService.rs256(
  privateKeyPem: File('private.pem').readAsStringSync(),
  publicKeyPem:  File('public.pem').readAsStringSync(),
  issuer: 'my-app',
  audience: 'api-clients',
);

Generating Tokens

final accessToken = jwtService.generateAccessToken(claims: {
  'sub': 'user-123',
  'username': 'akash',
});

final refreshToken = jwtService.generateRefreshToken(accessToken: accessToken);

Verifying Tokens

Both methods are async and return null on any failure (expired, wrong issuer, invalid signature, revoked):

final payload = await jwtService.verifyAccessToken(accessToken);
if (payload == null) {
  // token is invalid
}

final refreshPayload = await jwtService.verifyRefreshToken(refreshToken);

Token Revocation

Inject a TokenStore to enable revocation. InMemoryTokenStore works for single-instance servers:

final jwtService = JwtService(
  accessTokenSecret: 'my-secret',
  refreshTokenSecret: 'my-refresh-secret',
  issuer: 'my-app',
  audience: 'api-clients',
  tokenStore: InMemoryTokenStore(),
);

await jwtService.revokeToken(accessToken);

final payload = await jwtService.verifyAccessToken(accessToken); // null

For distributed deployments, implement TokenStore against Redis or a database:

class RedisTokenStore implements TokenStore {
  final RedisClient client;
  RedisTokenStore(this.client);

  @override
  Future<void> revoke(String jti) => client.set('revoked:$jti', '1');

  @override
  Future<bool> isRevoked(String jti) async =>
      await client.get('revoked:$jti') != null;
}

Protecting Routes

Pass authMiddleware to any route's middlewares list:

ApiRoute<void, List<UserDTO>>(
  method: ApiMethod.get,
  path: '/users',
  typedHandler: getUsers,
  middlewares: [authMiddleware(jwtService)],
);

The verified JWT payload is available in the handler via request.context['user']:

Future<UserDTO> getProfile(Request request, void _) async {
  final user = request.context['user'] as Map<String, dynamic>;
  final userId = user['sub'] as String;
  // ...
}

API Key Middleware

Use apiKeyMiddleware to protect routes with a static key — suitable for webhooks or internal service-to-service calls:

ApiRoute(
  method: ApiMethod.post,
  path: '/webhooks/stripe',
  middlewares: [
    apiKeyMiddleware(validKeys: {'whsec_abc123'}),
  ],
  typedHandler: handleStripeWebhook,
)

The default header is X-API-Key. Override with headerName:

apiKeyMiddleware(
  validKeys: {'my-internal-key'},
  headerName: 'X-Internal-Token',
)

The validated key is stored in request.context['api_key'] for downstream handlers.



License

BSD 3-Clause License © 2025 Akash G Krishnan

Libraries

dartapi_auth
DEPRECATED: dartapi_auth has been merged into dartapi_core ^0.1.0.