Skip to content

Package

JWTKit

🔑 JSON Web Token signing and verification (HMAC, RSA, PSS, ECDSA, EdDSA) using SwiftCrypto.

Overview

Use the SPM string to easily include the dependendency in your Package.swift file

.package(url: "https://github.com/vapor/jwt-kit.git", from: "5.0.0")

and add it to your target’s dependencies:

.product(name: "JWTKit", package: "jwt-kit")

Supported Platforms

JWTKit supports all platforms supported by Swift 6 and later.

Overview

JWTKit provides APIs for signing and verifying JSON Web Tokens, as specified by RFC 7519. The following features are supported:

  • Signing and Verification with Custom Headers

  • Customisable Parsing and Serialization

  • JSON Web Keys (JWK, JWKS)

The following algorithms, as defined in RFC 7518 § 3 and RFC 8037 § 3, are supported for both signing and verification:

JWS

Algorithm

Description

HS256

HMAC256

HMAC with SHA‑256

HS384

HMAC384

HMAC with SHA‑384

HS512

HMAC512

HMAC with SHA‑512

RS256

RSA256

RSASSA‑PKCS1‑v1_5 + SHA‑256

RS384

RSA384

RSASSA‑PKCS1‑v1_5 + SHA‑384

RS512

RSA512

RSASSA‑PKCS1‑v1_5 + SHA‑512

PS256

RSA256PSS

RSASSA‑PSS + SHA‑256

PS384

RSA384PSS

RSASSA‑PSS + SHA‑384

PS512

RSA512PSS

RSASSA‑PSS + SHA‑512

ES256

ECDSA256

P‑256 + SHA‑256

ES384

ECDSA384

P‑384 + SHA‑384

ES512

ECDSA512

P‑521 + SHA‑512

EdDSA

EdDSA

Ed25519

ML-DSA-65

MLDSA65

MLDSA with parameter set 65

ML-DSA-87

MLDSA87

MLDSA with parameter set 87

none

None

No signature / MAC

Vapor

The vapor/jwt package provides first-class integration with Vapor and is recommended for all Vapor projects which want to use JWTKit.

Getting Started

A JWTKeyCollection object is used to load signing keys and keysets, and to sign and verify tokens:

import JWTKit

#if !canImport(Darwin)
import FoundationEssentials
#else
import Foundation
#endif

// Signs and verifies JWTs
let keys = JWTKeyCollection()

To add a signing key to the collection, use the add method for the respective algorithm:

// Registers an HS256 (HMAC-SHA-256) signer.
await keys.add(hmac: "secret", digestAlgorithm: .sha256)

This example uses the very secure key "secret".

You can also add an optional key identifier (kid) to the key:

// Registers an HS256 (HMAC-SHA-256) signer with a key identifier.
await keys.add(hmac: "secret", digestAlgorithm: .sha256, kid: "my-key")

This is useful when you have multiple keys and need to select the correct one for verification. Based on the kid defined in the JWT header, the correct key will be selected for verification. If you don’t provide a kid, the key will be added to the collection as default.

To ensure thread-safety, JWTKeyCollection is an actor. This means that all of its methods are async and must be awaited.

Signing

We can generate JWTs, also known as signing. To demonstrate this, let’s create a payload. Each property of the payload type corresponds to a claim in the token. JWTKit provides predefined types for all of the claims specified by RFC 7519, as well as some convenience types for working with custom claims. For the example token, the payload looks like this:

struct ExamplePayload: JWTPayload {
    var sub: SubjectClaim
    var exp: ExpirationClaim
    var admin: BoolClaim

    func verify(using _: some JWTAlgorithm) throws {
        try self.exp.verifyNotExpired()
    }
}

Then, pass the payload to JWTKeyCollection.sign.

// Sign the payload, returning the JWT as String
let jwt = try await keys.sign(payload, header: ["kid": "my-key"])
print(jwt)

Here we’ve added a custom header to the JWT. Any key-value pair can be added to the header. In this case the kid will be used to look up the correct key for verification from the JWTKeyCollection.

You should see a JWT printed. This can be fed back into the verify method to access the payload.

Verifying

Let’s try to verify the following example JWT:

let exampleJWT = """
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ2YXBvciIsImV4cCI6NjQwOTIyMTEyMDAsImFkbWluIjp0cnVlfQ.lS5lpwfRNSZDvpGQk6x5JI1g40gkYCOWqbc3J_ghowo
    """

You can inspect the contents of this token by visiting jwt.io and pasting the token in the debugger. Set the key in the “Verify Signature” section to secret.

To verify a token, the format of the payload must be known. In this case, we know that the payload is of type ExamplePayload. Using this payload, the JWTKeyCollection object can process and verify the example JWT, returning its payload on success:

// Parse the JWT, verifies its signature, and decodes its content
let payload = try await keys.verify(exampleJWT, as: ExamplePayload.self)
print(payload)

If all works correctly, this code will print something like this:

TestPayload(
    sub: SubjectClaim(value: "vapor"),
    exp: ExpirationClaim(value: 4001-01-01 00:00:00 +0000),
    admin: BoolClaim(value: true)
)

Note

The admin property of the example payload did not have to use the BoolClaim type; a simple Bool would have worked as well. The BoolClaim type is provided by JWTKit for convenience in working with the many JWT implementations which encode boolean values as JSON strings (e.g. "true" and "false") rather than using JSON’s true and false keywords.

JWK

A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data structure that represents a cryptographic key, defined in RFC7517. These are commonly used to supply clients with keys for verifying JWTs. For example, Apple hosts their Sign in with Apple JWKS at the URL https://appleid.apple.com/auth/keys.

You can add this JSON Web Key Set (JWKS) to your JWTSigners:

import JWTKit

#if !canImport(Darwin)
import FoundationEssentials
#else
import Foundation
#endif

let rsaModulus = "..."

let json = """
    {
        "keys": [
            {"kty": "RSA", "alg": "RS256", "kid": "a", "n": "\(rsaModulus)", "e": "AQAB"},
            {"kty": "RSA", "alg": "RS512", "kid": "b", "n": "\(rsaModulus)", "e": "AQAB"},
        ]
    }
    """

// Create key collection and add JWKS
let keys = try await JWTKeyCollection().add(jwksJSON: json)

You can now pass JWTs from Apple to the verify method. The key identifier (kid) in the JWT header will be used to automatically select the correct key for verification. A JWKS may contain any of the key types supported by JWTKit.

HMAC

HMAC is the simplest JWT signing algorithm. It uses a single key that can both sign and verify tokens. The key can be any length.

To add an HMAC key to the key collection, use the addHS256, addHS384, or addHS512 methods:

// Registers an HS256 (HMAC-SHA-256) signer.
await keys.add(hmac: "secret", digestAlgorithm: .sha256)

Important

Cryptography is a complex topic, and the decision of algorithm can directly impact the integrity, security, and privacy of your data. This README does not attempt to offer a meaningful discussion of these concerns; the package authors recommend doing your own research before making a final decision.

ECDSA

ECDSA is a modern asymmetric algorithm based on elliptic curve cryptography. It uses a public key to verify tokens and a private key to sign them.

You can load ECDSA keys using PEM files:

let ecdsaPublicKey = "-----BEGIN PUBLIC KEY----- ... -----END PUBLIC KEY-----"

// Initialize an ECDSA key with public pem.
let key = try ES256PublicKey(pem: ecdsaPublicKey)

Once you have an ECDSA key, you can add to the key collection using the following methods:

  • addES256: ECDSA with SHA-256

  • addES384: ECDSA with SHA-384

  • addES512: ECDSA with SHA-512

await keys.add(ecdsa: key)

EdDSA

EdDSA is a modern algorithm that is considered to be more secure than RSA and ECDSA. It is based on the Edwards-curve Digital Signature Algorithm. The only currently supported curve by JWTKit is Ed25519.

You can create an EdDSA key using its coordinates:

// Initialize an EdDSA key with public PEM
let publicKey = try EdDSA.PublicKey(x: "...", curve: .ed25519)

// Initialize an EdDSA key with private PEM
let privateKey = try EdDSA.PrivateKey(d: "...", curve: .ed25519)

// Add public key to the key collection
await keys.add(eddsa: publicKey)

// Add private key to the key collection
await keys.add(eddsa: privateKey)

MLDSA

Hidden behind the @_spi(PostQuantum) flag, JWTKit supports MLDSA (Module-Lattice-Based Digital Signature Algorithm), a post-quantum signature scheme based on the CRYSTALS-DILITHIUM algorithm. It is currently behind an SPI flag because, while the MLDSA signature scheme is standardized by NIST, its usage in JWT is still in draft state, and, while unlikely, may change before being finalized. Therefore JWTKit reserves the ability to make breaking changes to this API until the usage of MLDSA in JWT is finalized.

Note

MLDSA is only available on macOS 26+.

Currently, to use MLDSA, you must import JWTKit with the @_spi(PostQuantum) flag enabled:

@_spi(PostQuantum) import JWTKit

Then you can choose whether to use MLDSA65 or MLDSA87. Use them as follows:

// Initialize an MLDSA key with its seed
let seedRepresentation = Data("...".utf8)
let privateKey = try MLDSA87PrivateKey(seedRepresentation: seedRepresentation)

// Add private key to the key collection
await keys.add(mldsa: privateKey)

RSA

RSA is an asymmetric algorithm. It uses a public key to verify tokens and a private key to sign them.

Warning

RSA is no longer recommended for new applications. If possible, use EdDSA or ECDSA instead. Infosec Insights’ June 2020 blog post “ECDSA vs RSA: Everything You Need to Know” provides a detailed discussion on the differences between the two.

To create an RSA signer, first initialize an RSAKey. This can be done by passing in the components:

// Initialize an RSA key with components.
let key = try Insecure.RSA.PrivateKey(
    modulus: "...",
    exponent: "...",
    privateExponent: "..."
)

The same initializer can be used for public keys without the privateExponent parameter.

You can also choose to load a PEM file:

let rsaPublicKey = "-----BEGIN PUBLIC KEY----- ... -----END PUBLIC KEY-----"

// Initialize an RSA key with public PEM
let key = try Insecure.RSA.PublicKey(pem: rsaPublicKey)

Use Insecure.RSA.PrivateKey(pem:) for loading private RSA pem keys and Insecure.RSA.PublicKey(certificatePEM:) for loading X.509 certificates. Once you have an RSA key, you can add to the key collection using the dedicated methods depending on the digest and the padding:

// Add RSA with SHA-256 algorithm
await keys.add(rsa: key, digestAlgorithm: .sha256)

// Add RSA with SHA-512 and PSS padding algorithm
await keys.add(pss: key, digestAlgorithm: .sha512)

Claims

JWTKit includes several helpers for implementing the “standard” JWT claims defined by RFC § 4.1:

Claim

Type

Verify Method

aud

AudienceClaim

verifyIntendedAudience(includes:)

exp

ExpirationClaim

verifyNotExpired(currentDate:)

jti

IDClaim

n/a

iat

IssuedAtClaim

n/a

iss

IssuerClaim

n/a

nbf

NotBeforeClaim

verifyNotBefore(currentDate:)

sub

SubjectClaim

n/a

Whenever possible, all of a payload’s claims should be verified in the verify(using:) method; those which do not have verification methods of their own may be verified manually.

Additional helpers are provided for common types of claims not defined by the RFC:

  • BoolClaim: May be used for any claim whose value is a boolean flag. Will recognize both boolean JSON values and the strings "true" and "false".

  • GoogleHostedDomainClaim: For use with the GoogleIdentityToken vendor token type.

  • JWTMultiValueClaim: A protocol for claims, such as AudienceClaim which can optionally be encoded as an array with multiple values.

  • JWTUnixEpochClaim: A protocol for claims, such as ExpirationClaim and IssuedAtClaim, whose value is a count of seconds since the UNIX epoch (midnight of January 1, 1970).

  • LocaleClaim: A claim whose value is a BCP 47 language tag. Also used by GoogleIdentityToken.

Custom Parsing and Serialization

The JWTParser and JWTSerializer protocols allow you to define custom parsing and serialization for your payload types. This is useful when you need to work with a non-standard JWT format.

For example you might need to set the b64 header to false, which does not base64 encode the payload. You can create your own JWTParser and JWTSerializer to handle this.

struct CustomSerializer: JWTSerializer {
    var jsonEncoder: JWTJSONEncoder = .defaultForJWT

    func serialize(_ payload: some JWTPayload, header: JWTHeader) throws -> Data {
        if header.b64?.asBool == true {
            try Data(self.jsonEncoder.encode(payload).base64URLEncodedBytes())
        } else {
            try self.jsonEncoder.encode(payload)
        }
    }
}

struct CustomParser: JWTParser {
    var jsonDecoder: JWTJSONDecoder = .defaultForJWT

    func parse<Payload>(_ token: some DataProtocol, as _: Payload.Type) throws -> (
        header: JWTHeader, payload: Payload, signature: Data
    ) where Payload: JWTPayload {
        let (encodedHeader, encodedPayload, encodedSignature) = try getTokenParts(token)

        let header = try jsonDecoder.decode(
            JWTHeader.self, from: .init(encodedHeader.base64URLDecodedBytes()))

        let payload =
            if header.b64?.asBool ?? true {
                try self.jsonDecoder.decode(Payload.self, from: .init(encodedPayload.base64URLDecodedBytes()))
            } else {
                try self.jsonDecoder.decode(Payload.self, from: .init(encodedPayload))
            }

        let signature = Data(encodedSignature.base64URLDecodedBytes())

        return (header: header, payload: payload, signature: signature)
    }
}

And then use them like this:

let keyCollection = await JWTKeyCollection()
    .add(hmac: "secret", digestAlgorithm: .sha256, parser: CustomParser(), serializer: CustomSerializer())

let payload = ExamplePayload(sub: "vapor", exp: .init(value: .init(timeIntervalSince1970: 2_000_000_000)), admin: false)

let token = try await keyCollection.sign(payload, header: ["b64": true])

Custom JSON Encoder and Decoder

If you don’t need to specify custom parsing and serializing but you do need to use a custom JSON Encoder or Decoder, you can use the the DefaultJWTParser and DefaultJWTSerializer types to create a JWTKeyCollection with a custom JSON Encoder and Decoder.

let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601

let parser = DefaultJWTParser(jsonDecoder: decoder)
let serializer = DefaultJWTSerializer(jsonEncoder: encoder)

let keyCollection = await JWTKeyCollection()
    .add(hmac: "secret", digestAlgorithm: .sha256, parser: parser, serializer: serializer)

This package was originally authored by the wonderful @siemensikkema.

Topics

Classes

  • JWTKeyCollection
    A collection of JWT and JWK signers for handling JSON Web Tokens (JWTs).

Protocols

  • ECDSACurveType
    A protocol defining the requirements for elliptic curve types used in ECDSA (Elliptic Curve Digital Signature Algorithm).
  • ECDSAKey
  • ECDSAPrivateKey
  • ECDSAPublicKey
  • ECDSASignature
  • ECDSASigningAlgorithm
  • EdDSAKey
    This protocol represents a key that can be used for signing and verifying EdDSA signatures. Both EdDSA.PublicKey and EdDSA.PrivateKey conform to this protocol.
  • JWTAlgorithm
    A protocol defining the necessary functionality for a JWT (JSON Web Token) algorithm. All algorithms conform to JWTAlgorithm to provide custom signing and verification logic for JWT tokens.
  • JWTClaim
    A claim is a codable, top-level property of a JWT payload. Multiple claims form a payload. Some claims, such as expiration claims, are inherently verifiable. Each claim able to verify itself provides an appropriate method for doing so, depending on the specific claim.
  • JWTJSONDecoder
  • JWTJSONEncoder
  • JWTMultiValueClaim
  • JWTParser
  • JWTPayload
    A JWT payload is a Publically Readable set of claims. Each variable represents a claim.
  • JWTSerializer
  • JWTUnixEpochClaim
  • RSAKey
    The RSAKey protocol defines the common interface for both public and private RSA keys. Implementers of this protocol can represent keys used for cryptographic operations in the RSA algorithm.
  • ValidationTimePayload
    A protocol defining the requirements for payloads that include a validation time.

Structures

  • AppleIdentityToken
  • AudienceClaim
    The “aud” (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the “aud” claim when this claim is present, then the JWT MUST be rejected. In the general case, the “aud” value is an array of case- sensitive strings, each containing a StringOrURI value. In the special case when the JWT has one audience, the “aud” value MAY be a single case-sensitive string containing a StringOrURI value. The interpretation of audience values is generally application specific. Use of this claim is OPTIONAL.
  • BoolClaim
    A claim which represents a bool
  • DefaultJWTParser
  • DefaultJWTSerializer
  • DigestAlgorithm
  • ECDSACurve
    A struct representing an Elliptic Curve used in Elliptic Curve Digital Signature Algorithm (ECDSA).
  • EdDSACurve
    A struct representing an Elliptic Curve used in EdDSA (Edwards-curve Digital Signature Algorithm).
  • EmptyPolicy
    This Policy acts as a placeholder. Its result is always positive.
  • ExpirationClaim
    The “exp” (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The processing of the “exp” claim requires that the current date/time MUST be before the expiration date/time listed in the “exp” claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.
  • FirebaseAuthIdentityToken
  • GoogleHostedDomainClaim
  • GoogleIdentityToken
  • HMACKey
  • IDClaim
    The “jti” (JWT ID) claim provides a unique identifier for the JWT. The identifier value MUST be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different data object; if the application uses multiple issuers, collisions MUST be prevented among values produced by different issuers as well. The “jti” claim can be used to prevent the JWT from being replayed. The “jti” value is a case- sensitive string. Use of this claim is OPTIONAL.
  • IssuedAtClaim
    The “iat” (issued at) claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.
  • IssuerClaim
    The “iss” (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The “iss” value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL.
  • JWK
    A JSON Web Key.
  • JWKIdentifier
  • JWKS
    A JSON Web Key Set.
  • JWTError
    JWT error type.
  • JWTHeader
    The header (details) used for signing and processing the JWT.
  • LocaleClaim
  • MicrosoftIdentityToken
  • NotBeforeClaim
    The “nbf” (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. The processing of the “nbf” claim requires that the current date/time MUST be after or equal to the not-before date/time listed in the “nbf” claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.
  • SubjectClaim
    The “sub” (subject) claim identifies the principal that is the subject of the JWT. The claims in a JWT are normally statements about the subject. The subject value MUST either be scoped to be locally unique in the context of the issuer or be globally unique. The processing of this claim is generally application specific. The “sub” value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL.
  • TenantIDClaim
    The “tid” (tenant ID) claim represents the unique identifier of the Azure AD tenant that the token was issued by. This claim is present in tokens when a user signs in to an application through Azure Active Directory. The tenant ID is a key piece of information for identifying the tenant realm and is essential for applications that are multi-tenant aware. The tenant ID is a GUID that is immutable and uniquely identifies an Azure AD tenant. This claim is crucial for applications that need to enforce tenant-specific access control, and for logging or auditing the tenant context of the authenticated user. The value of “tid” is a case-sensitive string representing a GUID. The presence of this claim and its proper validation are critical for the security of multi-tenant applications.
  • X5CVerifier
    An object for verifying JWS tokens that contain the x5c header parameter with a set of known root certificates.

Type Aliases

Enumerations

  • ECDSA
  • EdDSA
    Namespace for the EdDSA (Edwards-curve Digital Signature Algorithm) signing algorithm. EdDSA is a modern signing algorithm that is efficient and fast.
  • Insecure
    A container for older, cryptographically insecure algorithms.
  • JWTHeaderField

Extended Modules