swift-microservices/swift-authentication-jwt
A bearer token as a JSON Web Token: issued with a private key, proved with the public one.
The types
| Type | Role | | --- | --- | | JWTIssuer<Payload> | a CredentialIssuer<Payload, String>: mints the token; the one process holding the private key | | JWTAuthenticator<Payload> | an Authenticator<String, Payload>: returns the verified payload or throws if authentication fails |
Both come from swift-authentication's shape and are built on jwt-kit. Each takes a JWTKeyCollection, so which algorithms and how many keys is the application's choice.
The token shape is yours
The package never reads your claims. It asks only for a JWTPayload, and leaves which claims to enforce to the payload's own verify(using:). Check the issuer, the audience, and the expiry there; the authenticator runs it after the signature verifies:
struct AppToken: JWTPayload {
enum CodingKeys: String, CodingKey {
case subject = "sub"
case issuer = "iss"
case audience = "aud"
case expirationDate = "exp"
case role
}
let subject: String
let issuer: String
let audience: String
let expirationDate: Date
let role: String
func verify(using algorithm: some JWTAlgorithm) throws {
try ExpirationClaim(value: expirationDate).verifyNotExpired()
try AudienceClaim(value: [audience]).verifyIntendedAudience(includes: "posts")
guard issuer == "https://auth.example.com" else {
throw JWTError.claimVerificationFailure(failedClaim: IssuerClaim(value: issuer), reason: "unexpected issuer")
}
}
}let signingKeys = JWTKeyCollection()
await signingKeys.add(eddsa: privateKey)
let issuer = JWTIssuer<AppToken>(keys: signingKeys)
let verificationKeys = JWTKeyCollection()
await verificationKeys.add(eddsa: publicKey)
let authenticator = JWTAuthenticator<AppToken>(keys: verificationKeys)
let token = try await issuer.issue(
for: AppToken(
subject: "alice",
issuer: "https://auth.example.com",
audience: "posts",
expirationDate: .now + 3600,
role: "user"
)
)
let claims = try await authenticator.authenticate(token)Authentication returns the verified payload. A token that fails verification, such as one with a bad signature or an expired claim, throws. A transport package such as swift-authentication-grpc reads the token off the call, applies the authenticator, and binds the result as a Principal<AppToken, String>. Requiring an identity and deciding what it may do are the application's decisions.
User authentication
mTLS secures service connections; JWTs authenticate users. Each receiving service verifies the original token's signature, issuer, audience, and expiry. Apply bearer authentication to user routes and RPC descriptors, forward the original token only to upstream user descriptors, and authorize the operation in the owning use case.
Requirements
Swift 6.3, macOS 15 or Linux. jwt-kit 5.7.1, swift-authentication 0.4.
Development
swift test
swift-format lint --strict --recursive Sources Tests # what the soundness check runsContributing
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.
Package Metadata
Repository: swift-microservices/swift-authentication-jwt
Default branch: main
README: README.md