Skip to content

JWT

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

Getting Started

The first step to using JWT is adding the dependency to your Package.swift.

// swift-tools-version:5.2
import PackageDescription

let package = Package(
    name: "my-app",
    dependencies: [
         // Other dependencies...
        .package(url: "https://github.com/vapor/jwt.git", from: "4.0.0"),
    ],
    targets: [
        .target(name: "App", dependencies: [
            // Other dependencies...
            .product(name: "JWT", package: "jwt")
        ]),
        // Other targets...
    ]
)

If you edit the manifest directly inside Xcode, it will automatically pick up the changes and fetch the new dependency when the file is saved. Otherwise, run swift package resolve to fetch the new dependency.

Configuration

The JWT module adds a new property jwt to Application that is used for configuration. To sign or verify JWTs, you will need to add a signer. The simplest signing algorithm is HS256 or HMAC with SHA-256.

import JWT

// Add HMAC with SHA-256 signer.
app.jwt.signers.use(.hs256(key: "secret"))

The HS256 signer requires a key to initialize. Unlike other signers, this single key is used for both signing and verifying tokens. Learn more about the available algorithms below.

Payload

Let's try to verify the following example JWT.

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.

We need to create a struct conforming to JWTPayload that represents the JWT's structure. We'll use JWT's included claims to handle common fields like sub and exp.

// JWT payload structure.
struct TestPayload: JWTPayload {
    // Maps the longer Swift property names to the
    // shortened keys used in the JWT payload.
    enum CodingKeys: String, CodingKey {
        case subject = "sub"
        case expiration = "exp"
        case isAdmin = "admin"
    }

    // The "sub" (subject) claim identifies the principal that is the
    // subject of the JWT.
    var subject: SubjectClaim

    // The "exp" (expiration time) claim identifies the expiration time on
    // or after which the JWT MUST NOT be accepted for processing.
    var expiration: ExpirationClaim

    // Custom data.
    // If true, the user is an admin.
    var isAdmin: Bool

    // Run any additional verification logic beyond
    // signature verification here.
    // Since we have an ExpirationClaim, we will
    // call its verify method.
    func verify(using signer: JWTSigner) throws {
        try self.expiration.verifyNotExpired()
    }
}

Verify

Now that we have a JWTPayload, we can attach the JWT above to a request and use req.jwt to fetch and verify it. Add the following route to your project.

// Fetch and verify JWT from incoming request.
app.get("me") { req -> HTTPStatus in
    let payload = try req.jwt.verify(as: TestPayload.self)
    print(payload)
    return .ok
}

The req.jwt.verify helper will check the Authorization header for a bearer token. If one exists, it will parse the JWT and verify its signature and claims. If any of these steps fail, a 401 Unauthorized error will be thrown.

Test the route by sending the following HTTP request.

GET /me HTTP/1.1
authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ2YXBvciIsImV4cCI6NjQwOTIyMTEyMDAsImFkbWluIjp0cnVlfQ.lS5lpwfRNSZDvpGQk6x5JI1g40gkYCOWqbc3J_ghowo

If everything worked, a 200 OK response will be returned and the payload printed:

TestPayload(
    subject: "vapor", 
    expiration: 4001-01-01 00:00:00 +0000, 
    isAdmin: true
)

Signing

This package can also generate JWTs, also known as signing. To demonstrate this, let's use the TestPayload from the previous section. Add the following route to your project.

// Generate and return a new JWT.
app.post("login") { req -> [String: String] in
    // Create a new instance of our JWTPayload
    let payload = TestPayload(
        subject: "vapor",
        expiration: .init(value: .distantFuture),
        isAdmin: true
    )
    // Return the signed JWT
    return try [
        "token": req.jwt.sign(payload)
    ]
}

The req.jwt.sign helper will use the default configured signer to serialize and sign the JWTPayload. The encoded JWT is returned as a String.

Test the route by sending the following HTTP request.

POST /login HTTP/1.1

You should see the newly generated token returned in a 200 OK response.

{
   "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ2YXBvciIsImV4cCI6NjQwOTIyMTEyMDAsImFkbWluIjp0cnVlfQ.lS5lpwfRNSZDvpGQk6x5JI1g40gkYCOWqbc3J_ghowo"
}

Authentication

For more information on using JWT with Vapor's authentication API, visit Authentication → JWT.

Algorithms

Vapor's JWT API supports verifying and signing tokens using the following algorithms.

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.

  • hs256: HMAC with SHA-256
  • hs384: HMAC with SHA-384
  • hs512: HMAC with SHA-512
// Add HMAC with SHA-256 signer.
app.jwt.signers.use(.hs256(key: "secret"))

RSA

RSA is the most commonly used JWT signing algorithm. It supports distinct public and private keys. This means that a public key can be distributed for verifying JWTs are authentic while the private key that generates them is kept secret.

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 = RSAKey(
    modulus: "...",
    exponent: "...",
    // Only included in private keys.
    privateExponent: "..."
)

You can also choose to load a PEM file:

let rsaPublicKey = """
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0cOtPjzABybjzm3fCg1aCYwnx
PmjXpbCkecAWLj/CcDWEcuTZkYDiSG0zgglbbbhcV0vJQDWSv60tnlA3cjSYutAv
7FPo5Cq8FkvrdDzeacwRSxYuIq1LtYnd6I30qNaNthntjvbqyMmBulJ1mzLI+Xg/
aX4rbSL49Z3dAQn8vQIDAQAB
-----END PUBLIC KEY-----
"""

// Initialize an RSA key with public pem.
let key = RSAKey.public(pem: rsaPublicKey)

Use .private for loading private RSA PEM keys. These start with:

-----BEGIN RSA PRIVATE KEY-----

Once you have the RSAKey, you can use it to create an RSA signer.

  • rs256: RSA with SHA-256
  • rs384: RSA with SHA-384
  • rs512: RSA with SHA-512
// Add RSA with SHA-256 signer.
try app.jwt.signers.use(.rs256(key: .public(pem: rsaPublicKey)))

ECDSA

ECDSA is a more modern algorithm that is similar to RSA. It is considered to be more secure for a given key length than RSA1. However, you should do your own research before deciding.

Like RSA, you can load ECDSA keys using PEM files:

let ecdsaPublicKey = """
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2adMrdG7aUfZH57aeKFFM01dPnkx
C18ScRb4Z6poMBgJtYlVtd9ly63URv57ZW0Ncs1LiZB7WATb3svu+1c7HQ==
-----END PUBLIC KEY-----
"""

// Initialize an ECDSA key with public PEM.
let key = ECDSAKey.public(pem: ecdsaPublicKey)

Use .private for loading private ECDSA PEM keys. These start with:

-----BEGIN PRIVATE KEY-----

You can also generate random ECDSA using the generate() method. This is useful for testing.

let key = try ECDSAKey.generate()

Once you have the ECDSAKey, you can use it to create an ECDSA signer.

  • es256: ECDSA with SHA-256
  • es384: ECDSA with SHA-384
  • es512: ECDSA with SHA-512
// Add ECDSA with SHA-256 signer.
try app.jwt.signers.use(.es256(key: .public(pem: ecdsaPublicKey)))

Key Identifier (kid)

If you are using multiple algorithms, you can use key identifiers (kids) to differentiate them. When configuring an algorithm, pass the kid parameter.

// Add HMAC with SHA-256 signer named "a".
app.jwt.signers.use(.hs256(key: "foo"), kid: "a")
// Add HMAC with SHA-256 signer named "b".
app.jwt.signers.use(.hs256(key: "bar"), kid: "b")

When signing JWTs, pass the kid parameter for the desired signer.

// Sign using signer "a"
req.jwt.sign(payload, kid: "a")

This will automatically include the signer's name in the JWT header's "kid" field. When verifying the JWT, this field will be used to look up the appropriate signer.

// Verify using signer specified by "kid" header.
// If no "kid" header is present, default signer will be used.
let payload = try req.jwt.verify(as: TestPayload.self)

Since JWKs already contain kid values, you do not need to specify them during configuration.

// JWKs already contain the "kid" field.
let jwk: JWK = ...
app.jwt.signers.use(jwk: jwk)

Claims

Vapor's JWT package includes several helpers for implementing common JWT claims.

Claim Type Verify Method
aud AudienceClaim verifyIntendedAudience(includes:)
exp ExpirationClaim verifyNotExpired(currentDate:)
jti IDClaim n/a
iat IssuedAtClaim n/a
iss IssuerClaim n/a
locale LocaleClaim n/a
nbf NotBeforeClaim verifyNotBefore(currentDate:)
sub SubjectClaim n/a

All claims should be verified in the JWTPayload.verify method. If the claim has a special verify method, you can use that. Otherwise, access the value of the claim using value and check that it is valid.

JWK

A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data structure that represents a cryptographic key (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 following URL.

GET https://appleid.apple.com/auth/keys

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

import JWT
import Vapor

// Download the JWKS.
// This could be done asynchronously if needed.
let jwksData = try Data(
    contentsOf: URL(string: "https://appleid.apple.com/auth/keys")!
)

// Decode the downloaded JSON.
let jwks = try JSONDecoder().decode(JWKS.self, from: jwksData)

// Create signers and add JWKS.
try app.jwt.signers.use(jwks: jwks)

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.

As of writing, JWK only supports RSA keys. Additionally, JWT issuers may rotate their JWKS meaning you need to re-download occasionally. See Vapor's supported JWT Vendors list below for APIs that do this automatically.

Vendors

Vapor provides APIs for handling JWTs from the popular issuers below.

Apple

First, configure your Apple application identifier.

// Configure Apple app identifier.
app.jwt.apple.applicationIdentifier = "..."

Then, use the req.jwt.apple helper to fetch and verify an Apple JWT.

// Fetch and verify Apple JWT from Authorization header.
app.get("apple") { req -> EventLoopFuture<HTTPStatus> in
    req.jwt.apple.verify().map { token in
        print(token) // AppleIdentityToken
        return .ok
    }
}

// Or

app.get("apple") { req async throws -> HTTPStatus in
    let token = try await req.jwt.apple.verify()
    print(token) // AppleIdentityToken
    return .ok
}

Google

First, configure your Google application identifier and G Suite domain name.

// Configure Google app identifier and domain name.
app.jwt.google.applicationIdentifier = "..."
app.jwt.google.gSuiteDomainName = "..."

Then, use the req.jwt.google helper to fetch and verify a Google JWT.

// Fetch and verify Google JWT from Authorization header.
app.get("google") { req -> EventLoopFuture<HTTPStatus> in
    req.jwt.google.verify().map { token in
        print(token) // GoogleIdentityToken
        return .ok
    }
}

// or

app.get("google") { req async throws -> HTTPStatus in
    let token = try await req.jwt.google.verify()
    print(token) // GoogleIdentityToken
    return .ok
}

Microsoft

First, configure your Microsoft application identifier.

// Configure Microsoft app identifier.
app.jwt.microsoft.applicationIdentifier = "..."

Then, use the req.jwt.microsoft helper to fetch and verify a Microsoft JWT.

// Fetch and verify Microsoft JWT from Authorization header.
app.get("microsoft") { req -> EventLoopFuture<HTTPStatus> in
    req.jwt.microsoft.verify().map { token in
        print(token) // MicrosoftIdentityToken
        return .ok
    }
}

// Or

app.get("microsoft") { req async throws -> HTTPStatus in
    let token = try await req.jwt.microsoft.verify()
    print(token) // MicrosoftIdentityToken
    return .ok
}

  1. https://www.ssl.com/article/comparing-ecdsa-vs-rsa/