---
title: swift-microservices/swift-openapi-token-authentication
framework: Swift Package Catalog
role: article
path: packages/swift-microservices/swift-openapi-token-authentication
---

# swift-microservices/swift-openapi-token-authentication

Access-token authentication and automatic refresh for clients generated by

## Usage

Implement `AuthenticationClient` with your API's login and refresh calls. Its response must conform to `AuthenticationResponse`, providing access and refresh tokens and their expiration dates. Use an authentication client without this middleware for those calls to avoid waiting on its own session.

```swift import OpenAPITokenAuthentication

let session = AuthenticationSession(     client: authenticationClient,     storage: InMemoryAuthenticationStorage<YourAuthenticationResponse>() )

try await session.authenticate(credentials: credentials)

let client = Client(     serverURL: serverURL,     transport: transport,     middlewares: [AuthenticationMiddleware(session: session)] ) ```

Share a session across clients using the same credentials. Access tokens refresh on demand within 30 seconds of expiration; customize this with `refreshLeeway`. Provide an `AuthenticationStorage` implementation if credentials must survive restarts.

### Anonymous requests

Authentication is required by default. For endpoints that also accept anonymous requests, opt in to attaching credentials only when a session is available:

```swift AuthenticationMiddleware(session: session, policy: .ifAvailable) ```

The middleware first calls `accessToken()`, waiting for any in-flight login (including QR pairing) or refresh. If token lookup throws `AuthenticationSessionError.userAuthenticationRequired`, `.ifAvailable` sends the request unchanged without adding an Authorization header. This includes both signed-out sessions and expired or rejected refresh credentials discovered during token lookup. Existing headers are preserved; an anonymous response, including HTTP 401, is returned without attempting refresh or retry.

Other token-lookup errors, including transport failures and cancellation, propagate. Once a token has been attached, the normal authenticated refresh and retry behavior applies: a failure during the request or its HTTP 401 recovery is not retried anonymously. The server still enforces authorization for protected endpoints. `accessToken()` itself remains strict; only the middleware applies this policy.

This policy does not change credential validation: the session still requires valid refresh credentials when accepting a login or restoring stored credentials.

### Refreshing requests

The middleware refreshes on HTTP 401 by default. Customize this with `refreshableStatusCodes` or a response predicate:

```swift AuthenticationMiddleware(session: session) { response, _ in     response.status == .unauthorized         && response.headerFields[.wwwAuthenticate]?.contains("invalid_token") == true } ```

Requests with single-use bodies are never retried. Only overlapping refreshes share work; a later rejected response can trigger another refresh. Cancel outstanding API requests before switching accounts, or use separate sessions for separate accounts.

The session also works without middleware:

```swift let token = try await session.accessToken() let refreshedToken = try await session.newAccessToken() await session.logout() ```

### Observing state

```swift let observation = Task {     for await state in await session.states() {         print(state) // unauthenticated, authenticating, authenticated, or refreshing     } }

observation.cancel() ```

Each listener receives the current state followed by changes, buffering only the latest state. Cancelling observation does not cancel authentication.

### Errors and cancellation

Classify rejected credentials in your authentication client with `AuthenticationClientError(.invalidCredentials)` or `.invalidRefreshToken`, optionally preserving the API error in `underlyingError`. Other errors propagate unchanged.

Missing, expired, or rejected refresh credentials clear the session and throw `AuthenticationSessionError.userAuthenticationRequired`. Temporary refresh failures preserve valid refresh credentials so a later call can retry.

Cancelling an endpoint request does not cancel shared authentication work or immediately interrupt its wait. Logout and replacement login cancel pending work and prevent stale results from restoring credentials.

## Requirements

Swift 6.3. Linux, macOS 15, iOS/Mac Catalyst/tvOS 18, watchOS 11, or visionOS 2. swift-openapi-runtime 1.12.1, swift-http-types 1.0.

## Development

```sh swift test swift-format lint --strict --recursive Sources Tests    # what the soundness check runs ```

## Contributing

Pull requests are welcome. Keep a change focused, prove new behaviour with a test, and label the pull request with its semantic version impact.

## License

MIT. See [LICENSE](LICENSE).

## Package Metadata

Repository: swift-microservices/swift-openapi-token-authentication

Default branch: main

README: README.md
