JSON Web Tokens and Vapor, Part 1: Creating JWTs

568 Views

This is the first part in a series on JSON Web Tokens and Vapor. If you don't know what a JSON web token is, and why you may want to use one, I suggest by [starting with this article](https://scotch.io/tutorials/the-anatomy-of-a-json-web-token) about the technical specifications and anatomy of JWTs. You should understand what a JWT is, and why it may be used as opposed to other types of authentication (Token auth or Basic HTTP auth, for example) before continuing. Otherwise, you should do further reading about them. Some reading I found useful:

Now, let's begin! First, download the basic starter project here. At this point the project is very simple, with a User model with email/password attributes. There is also an empty implementation of a method to create a user.

Add the following to your Package.json file:

    .Package(url:"https://github.com/vapor/jwt.git", majorVersion: 2)

Close Xcode, run vapor update and then regenerate your Xcode project, then open your project again.

Create a new file, and call it TokenHelpers. Add the following code to it:

    import JWT // (1)
    struct JWTConfig { // (2)
      static let signerKey = "yourSignerKeyHere" 
      static let headers = JSON(["typ": "JWT", "alg": "HS256"])
      static let signer = HS256(key: JWTConfig.signerKey.bytes)
      static let expirationTime: Int = 1000 // (3)
    }

The signerKey should be an environment variable, and we will fix that later. Now, for simplicity, we will just keep its value in the struct.

(1) Import the JWT framework (2) This is the struct we will use to keep configuration elements for our JWT. There are other types of hashing functions, but we will use "HS256" in this example. (3) Is the expiration time, in seconds, for our JSON Web Token to last.

Now, outside the struct definition, add the following to TokenHelpers.swift:

    class TokenHelpers {
        class func createPayload(from user: User) throws -> JSON { // (1)
        if let id = user.id?.int {
            let now = Date()
            let dateAsTimeDouble = now.timeIntervalSince1970 // (2)
            let createdAt:Int = Int(dateAsTimeDouble)
            let expiration = Int(dateAsTimeDouble) + JWTConfig.expirationTime // (3)
            let payLoad = JSON(["iss": "vaporforums", "iat": .number(.int(createdAt)), "userEmail": .string(user.username), "userId": .number(.int(id)), "exp": .number(.int(expiration))]) // (4)
            return payLoad
        } else { throw JWTError.createKey }
      }
    }

(1) this is a function that will take a given user, and create a JWT payload from them. (2) Dates in JWTs are unix time stamps. The now variable refers to the date at this very second. dateTimeAsDouble creates a unix timestamp of the date right now. We then convert that from a double to an int, and then add our previously defined expiration time to define the unix timestamp at which this JWT will expire.

The unix timestamp for the expiration date is (3). At (4), we create a payload. iss, iat and exp stand for "issuer", "issued at time" and "expiration" respectively. The expiration key must be exp, or else you can not verify ExpirationTime claims.

Next, add the following function to the TokenHelpers class:

    class func createJwt(from user: User) throws -> String { // (1)
        do {
            let payLoad = try TokenHelpers.createPayload(from: user) // (2)
            let headers = JWTConfig.headers
            let signer = JWTConfig.signer
            let jwt = try JWT(headers: headers, payload: payLoad, signer: signer) // (3)
            let token = try jwt.createToken() // (4)
            return token
        } catch { throw JWTError.createKey }
    }

(1) This function will take a given user, and create a JWT for them. (2) We use the previously defined function to create the payload. (3) We initialize our JWT with the headers, payload, and signer. (4) We use the JWT library to create a token with the given information, and then return the token as a String.

Now, go to UserController.swift, and find the createUser function. Replace its contents with the following:

    func createUser(request: Request) throws -> ResponseRepresentable {
        if let username = request.data[User.nameKey]?.string,
            let password = request.data[User.passwordKey]?.string {
            do { let passwordHashed = try User.hasher.make(password)
                let user = try User(username: username, password: passwordHashed)
                try user.save() // (1)
                let token = try TokenHelpers.createJwt(from: user) // (2)
                return try JSON(node: ["token": token])
            } catch let error { return try JSON(node: ["error": error])}
        }
        else { return "There was an error" }
    }

This function gets the email and password from the request, and then at (1) we create a user from the data (after hashing the password). At (2), we make use of our previously defined functions, and create a JWT from the saved user. Make a POST request to /createuser with the email and password, and you should get a response, like so:

     { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ2YXBvcmZvcnVtcyIsImlhdCI6MTUwNDY4OTY5OSwidXNlcklkIjoyLCJleHAiOjE1MDQ2OTA2OTksInVzZXJuYW1lIjoiaWxvdmVjYXRzQHlhaG9vLmZyIn0.tIXH0FsMfzpcsSaAmQB4WcBaxYxVcPlc08vko73XFPY" }

As of now, you can register a user, and create a JWT for that user with an expiration time claim, and some basic info about that user such as their userId and email. In the next part, we will talk about verifying claims made by JWTs, signing, and creating some custom middleware to hide routes that invalid JWTs attempt to access.



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