apiKeyAuthentication function

Middleware apiKeyAuthentication({
  1. required AuthApiKeyPlugin<EngineContext> plugin,
  2. required AuthUserStore userStore,
  3. String headerName = 'x-api-key',
  4. FutureOr<void> onVerified(
    1. EngineContext context,
    2. AuthApiKeyAuthentication authentication,
    3. AuthUser user
    )?,
})

Authenticates requests carrying an API key.

The middleware accepts X-API-Key: <key> by default and also accepts Authorization: ApiKey <key>. A missing key leaves the request untouched, allowing applications to compose API-key, session, and JWT middleware. An invalid supplied key returns a generic 401 response with an ApiKey challenge. headerName is trimmed and must not be empty. After successful user lookup, onVerified runs before next, and the request receives the API-key authentication plus an AuthPrincipal with apiKeyId and apiKeyScopes attributes. Invalid credentials do not disclose whether a key or user exists; exceptions from the authentication plugin or user store propagate to the surrounding middleware pipeline.

Throws an ArgumentError when headerName is empty after trimming.

Implementation

Middleware apiKeyAuthentication({
  required AuthApiKeyPlugin<EngineContext> plugin,
  required AuthUserStore userStore,
  String headerName = 'x-api-key',
  FutureOr<void> Function(
    EngineContext context,
    AuthApiKeyAuthentication authentication,
    AuthUser user,
  )?
  onVerified,
}) {
  final normalizedHeader = headerName.trim();
  if (normalizedHeader.isEmpty) {
    throw ArgumentError.value(headerName, 'headerName', 'must not be empty');
  }

  return (EngineContext ctx, Next next) async {
    final request = parseApiKeyRequest(ctx, headerName: normalizedHeader);
    if (request.malformed) return _invalidApiKey(ctx);
    final rawKey = request.value;
    if (rawKey == null) return next();

    final authentication = await plugin.authenticate(rawKey);
    final user = authentication == null
        ? null
        : await userStore.findById(authentication.record.userId);
    if (authentication == null || user == null) {
      return _invalidApiKey(ctx);
    }

    ctx.request.setAttribute(authApiKeyAuthenticationAttribute, authentication);
    ctx.request.setAttribute(
      authPrincipalAttribute,
      AuthPrincipal(
        id: user.id,
        roles: user.roles,
        attributes: {
          'apiKeyId': authentication.record.id,
          'apiKeyScopes': authentication.record.scopes,
        },
      ),
    );
    await onVerified?.call(ctx, authentication, user);
    return next();
  };
}