The last time I got to yammering on, I mentioned I was building a side project in Vapor 3. When I got to setting up authentication for this project, I decided to use JWTs for reasons that are probably inexplicable to anyone with a clue.

Fortunately my cluelessness didn’t deter me. I quickly found that Vapor has an Authentication framework and a JWT framework. Unfortunately, I also found that they don’t work together out of the box, and there’s not much documentation on either. Cue my patented flailing around and trying to grok someone else’s code. Eventually, figured out something that worked.

Here’s my plan for marrying JWT with Authentication: first, figure out what to put in my JWT. Then I’ll need some code to authenticate that JWT, and some middleware to call it. Finally, I’ll use the results of the middleware to see if the holder of the JWT is authorized to perform the request they made.

Defining the JWT Payload

First, I had to define what my JWT looked like. A JWT is essentially a signed JSON payload, so I had to decide what should go in that payload. The main thing my app needed to know was the user making the request.

Here’s an abridged version of what I ended up doing:


import Foundation
import Vapor
import JWT
import Authentication

struct UserIDClaim: JWTClaim {
    var value: UUID
}

struct AuthJWTPayload: JWTPayload {
    func verify(using signer: JWTSigner) throws {
        // nothing for now
    }

    let uid: UserIDClaim
    let iat: IssuedAtClaim
}

The JWTPayload is a protocol defined in the JWT framework. Each property in the payload is supposed to be a “claim” that can be verified, and the verify method is supposed to do the verifying. However, since the verify method only takes the signer, there’s not much verifying it can actually do. I suppose it could verify that the IssuedAtClaim isn’t older than a given date.

Speaking of claims, there’s a standard set that the JWT framework defines. The IssuedAtClaim is an example. However, it’s possible to define your own, which is what I did with UserIDClaim. As the name suggests, it represents the user ID that the holder of the JWT operates as. All JWTClaims must be Codable, and define a mutable property named value.

The weird three letter property names appear to be a convention for claims. My assumption is it’s to keep the resulting JSON, and therefore the JWT, small.

Verifying a JWT Payload

Now that I had a payload defined, I needed something to verify it. The Authentication framework defines this kind of type as Authenticatable. I decided to be a bit fancy and define a protocol for JWT authenticatables before I wrote an implementation to authenticate my specific JWT payload.


import Foundation
import Vapor
import Authentication
import JWT

protocol JWTAuthenticatable: Authenticatable {
    associatedtype JWTType: JWTPayload

    static func authenticate(jwt: JWTType, on connection: DatabaseConnectable) -> Future<Self?>
}

The Authenticatable protocol itself has no constraints. I had JWTAuthenticatable declare a generic type that conforms to JWTPayload, and a static authenticate method to verify it. The return value will be an instance of the JWTAuthenticatable if the payload validates, nil otherwise.

Now I could define my actual JWTAuthenticatable type to verify the JWT payload:


import Foundation
import Vapor
import Authentication
import JWT

struct AuthJWTAuthenticatable: JWTAuthenticatable {
    typealias JWTType = AuthJWTPayload

    let user: User

    static func authenticate(jwt: JWTType, on connection: DatabaseConnectable) -> Future<AuthJWTAuthenticatable?> {
        return User.query(on: connection)
            .filter(\.id == jwt.uid.value)
            .first()
            .map { maybeUser in
                return maybeUser.map { AuthJWTAuthenticatable(user: $0) }
            }
    }

}

There are a couple of things to note here. First, if authenticate returns an instance of Authenticatable, the middleware (described later) is going to stash it on the request. Meaning Authenticatable is a great place to stash information about the authenticated “caller” because it can be accessed by other middleware or controllers. In my case, the caller represents a user, so I stash the user on the Authenticatable so my controllers can use it later.

The second thing of note is that I’m hitting the database to verify the JWT. This negates one of benefits of using a JWT, which is being able to verify a token without hitting the database. However, for my app I always need to have the user struct in memory, so it’s easier to go ahead and load it here. An alternative approach to avoid hitting the database during authentication would be:


struct AuthJWTAuthenticatable: JWTAuthenticatable {
    typealias JWTType = AuthJWTPayload

    let userId: UUID

    static func authenticate(jwt: JWTType, on connection: DatabaseConnectable) -> Future<AuthJWTAuthenticatable?> {
        return AuthJWTAuthenticatable(userId: jwt.uid.value)
    }
}

With this approach, I’d have to fetch the user in each controller that needed the full User.

Creating Some Middleware to Authenticate Incoming Requests

Now that I have a payload and the ability to authenticate it, I need to hook into the request handling and authenticate all incoming requests. As with most web frameworks, Vapor provides middleware as a solution for hooking into the request handling. With that in mind, here’s my middleware for authenticating incoming requests:


import Foundation
import Vapor
import JWT
import Authentication

struct JWTAuthenticationMiddleware<A: JWTAuthenticatable>: Middleware {

    func respond(to req: Request, chainingTo next: Responder) throws -> Future<Response> {
        // If the request is already authenticated, go to next middleware
        if try req.isAuthenticated(A.self) {
            return try next.respond(to: req)
        }

        // This parses the JWT string from the Authorization header
        guard let tokenString = req.http.headers.bearerAuthorization?.token else {
            // If there isn't a header, that's fine. Just go to the next middleware
            return try next.respond(to: req)
        }
        // Use my helper type to decode the string into my JWTPayload struct
        let token = try JWTSerialization<A.JWTType>().decode(tokenString, on: req)

        // Ask the JWTAuthenticatable if this is a valid JWT
        return A.authenticate(jwt: token, on: req).flatMap { a in
            if let a = a {
                // The JWT was valid, so this method call stashes the JWTAuthenticatable on the request
                try req.authenticate(a)
            }
            // Regardless of if the JWT is valid or not, go to the next middleware
            return try next.respond(to: req)
        }
    }
}

extension JWTAuthenticationMiddleware: ServiceType {
    static func makeService(for worker: Container) throws -> JWTAuthenticationMiddleware {
        return JWTAuthenticationMiddleware()
    }
}

I based my authentication middleware on the authentication middlewares defined in the Authentication framework. The main difference is my middleware uses my JWTAuthenticatable protocol to do the authentication. The goal of the middleware is to authenticate any Authorization header the request might have, and, if valid, store the resulting Authenticatable on the request for future use. Notably, the middleware doesn’t try to authorize the caller (i.e. it doesn’t require a valid Authorization header). This is because I don’t yet know at this stage what the request is trying to do, and what level of authorization that would require.

I factored out the JWT deserialization into a helper type to keep the middleware a bit more focused. Here’s what the deserialization code looks like:


import Foundation
import Vapor
import JWT

enum AuthJWTError: Error {
    case invalidDataEncoding
}

struct JWTSerialization<T: JWTPayload> {

    func encode(_ payload: T, on container: Container) throws -> String {
        let jwt = JWT(payload: payload)
        let config = try container.make(JWTConfig.self)
        let encoded = try jwt.sign(using: config.signer())
        guard let jwtString = String(data: encoded, encoding: .utf8) else {
            throw AuthJWTError.invalidDataEncoding
        }
        return jwtString
    }

    func decode(_ string: String, on container: Container) throws -> T {
        let config = try container.make(JWTConfig.self)
        let jwt = try JWT<T>(from: string, verifiedUsing: config.signer())
        return jwt.payload
    }
}

There’s not much to the decoding of JWTs. The JWT type is defined by Vapor’s JWT framework and can both sign and verify the signature of a JWT. The JWTConfig is a type that I created so I could abstract out the signing key and signing algorithm. I used Vapor’s dependency injection mechanism to fetch it. Here’s what it looks like:


import Foundation
import Vapor
import JWT

protocol JWTConfig: Service {
    func signer() throws -> JWTSigner
}

class HmacJWTConfig: JWTConfig {
    private let apiConfig: ApiConfigurationType

    public init(apiConfig: ApiConfigurationType) {
        self.apiConfig = apiConfig
    }

    func signer() throws -> JWTSigner {
        return try JWTSigner.hs256(key: apiConfig.jwtKey.hexEncodedData())
    }
}

The JWTConfig is a Service protocol that creates a JWTSigner (defined in the JWT framework) on demand. I only have one concrete implementation in my app: HmacJWTConfig. HmacJWTConfig is pretty simple; it creates a JWTSigner.hs256 signer (which uses HMAC-SHA256) with a key it pulls from the app config. The config implementation isn’t that interesting; on production it pulls a hex encoded key from an environment variable.

I’ve almost got my app where it can authenticate any incoming requests. The last piece is the configuration:


try services.register(AuthenticationProvider())
services.register(JWTAuthenticationMiddleware<AuthJWTAuthenticatable>.self)
services.register(HmacJWTConfig(apiConfig: configuration.api), as: JWTConfig.self)
...
middlewares.use(JWTAuthenticationMiddleware<AuthJWTAuthenticatable>.self)
...

This is simply registering all the services and middleware I defined earlier.

Using the Authenticatable

Finally, I’m ready to use the Authenticatable to see if a request is properly authorized. For my app, the requests coming in are user-scoped, meaning the user ID being acted on is in the request URL. A user should only be able to act on their own account. Therefore, my authorization method should make sure the request’s user ID parameter matches my JWT payload’s user ID.

Here’s what I do in my controller:


private func authorizedUser(_ request: Request) throws -> User {
    let user = try request.requireAuthenticated(AuthJWTAuthenticatable.self).user
    let userIdParameter = try request.parameters.next(UUID.self)
    guard userIdParameter == user.id else {
        throw Abort(.unauthorized, reason: "Unauthorized")
    }
    return user
}

The first line pulls out the AuthJWTAuthenticatable that my middleware stashed there. If it’s missing, then requireAuthenticated will throw an error. Next, I pull the request’s user ID parameter out. Finally, I compare the two to make sure they match. If they don’t, I abort with an unauthorized error.

This isn’t the only way to handle authorization. If I just wanted to make sure certain routes had authentication of some kind (i.e. requireAuthenticated doesn’t throw an error), then I could make use of GuardAuthenticationMiddleware defined in the Authentication framework.

And that’s about it for using JWTs to do authentication in Vapor 3. While Vapor has all the tools for JWT and authentication, they’re not put together in a way that works out of the box. For that reason, I’ve done a walkthrough here on how I put them together in my app.