JSON Web Tokens and Vapor Part 2: Verifying claims and Signing

548 Views

This is part 2 of JSON Web Tokens and Vapor. If you didn't complete part 1, then you can download the completed part 1 here. In this part, we will focus on verifying claims in JWTs. It's actually easier than you think!

First, let's verify the signature. In TokenHelpers.swift, add the following function:

    class func canVerifySignature(withSigner signer: String, fromToken token: String) -> Bool {
        do {
            let receivedJWT = try JWT(token: token) // (1) 
            try receivedJWT.verifySignature(using: HS256(key: signer.bytes)) // (2)
            return true
        }
        catch { return false }
    }

Here, we simply init a JWT with the given string in (1). Then in (2) we verify the signature using the key passed in. We can test this. To test the functionality, first, create a route in UserController.swift. In addRoutes(), add:

    droplet.get("verify", handler: verifyToken)

Then, add the following function in UserController to verify:

    func verifyToken(request: Request) throws -> ResponseRepresentable {
        if let jwt = request.headers["Authorization"]?.string {
            let verified = TokenHelpers.canVerifySignature(withSigner: JWTConfig.signerKey, fromToken: jwt)
            return try JSON(node: ["verified": verified])
        } else { return "Please pass token in Authorization Header" }
    }

Create a user, and then make a GET request to /verify with the JWT set as the Authorization header. The first time, pass in the signer as the correct one, like:

    TokenHelpers.canVerifySignature(withSigner: JWTConfig.signerKey, fromToken: jwt)

The second time, pass in an incorrect signing key:

    TokenHelpers.canVerifySignature(withSigner: "wrong signer key here", fromToken: jwt)

And it will return the correct true/false value depending on if it is verified. While you can technically still use the payload from a JWT which can not be verified, it is inadvisable, because the entire reason for a signer is to ensure that the JWT has not been tampered with since it's creation (so you know you're getting an authentic key produced by your server). If that fails, then you should blacklist the token and invalidate it, and not proceed with the request.

Next, let's verify claims made by JWTs. Add the following function to `TokehHelpers.swift`:

    class func tokenIsExpired(_ token: String) -> Bool {
         do { let receivedJWT = try JWT(token: token)
            try receivedJWT.verifyClaims([ExpirationTimeClaim(date: Date())]) // (1)
            return false
         } catch { return true }
     }

This function takes a given token, and verifies that it is still valid. At (1), we pass in the Date() object, meaning that we are verifying the exp claim in the payload is still valid right now. Then, we return false if the verification passes (meaning yes it verifies, therefore token is NOT expired), and return true if it fails, meaning the token is no longer valid.

Next, let's verify the issuer claim, iss:

    class func verifyIssuer(_ token: String) -> Bool {
        do { let receivedJWT = try JWT(token: token)
            let issuerClaim = IssuerClaim(string: "vaporforums") // the string here will be your `iss` in the payload
            try receivedJWT.verifyClaims([issuerClaim])
            return true
        } catch { return false }
    }

This simply takes an IssuerClaim (there is a full list of different claims available to verify here) and attempts to verify it. If it matches the one given in your payload, it will pass, and the method returns true, other wise fail and return false.

Finally, let's add a method that gathers all these, and can tell us if the token itself is verified. Still in TokenHelpers.swift:

    class func tokenIsVerified(_ token: String) -> Bool {
        let expired = TokenHelpers.tokenIsExpired(token)
        let issuerVerified = TokenHelpers.verifyIssuer(token)
        let signatureVerified = TokenHelpers.canVerifySignature(withSigner: JWTConfig.signerKey, fromToken: token)
        if (!expired && issuerVerified && signatureVerified) {
            return true
        } else { return false }
    }
    

This simply takes the tokenIsExpired method, the verifyIssuer method, and the canVerifySignaturemethod, and if all pass, returns true. For testing purposes, you can update the verifyToken function in UserController.swift to:

    func verifyToken(request: Request) throws -> ResponseRepresentable {
        if let jwt = request.headers["Authorization"]?.string {
            let verified = TokenHelpers.tokenIsVerified(jwt)
            return try JSON(node: ["verified": verified])
        } else { return "Please pass token in Authorization Header" }
    }

And it will return to you the status of your JSON Web Token.

That's it for part 2! In part 3, we will look at adding custom middleware to wrap routes in, to make sure users aren't accessing routes they aren't authorized to.



Enjoy this article? Consider supporting VaporForums by following us on Twitter! Get the latest Vapor articles, tutorials, and news.