JSON Web Token Authentication

A JSON Web Token (JWT) defines a compact and self-contained way for securely transmitting information between parties as a JSON object. You can find out more about JWTs at JWT.IO.

Swift-JWT is our implementation of JSON Web Token using Swift. It allows you to create, sign and verify JWTs on iOS, macOS and Linux using a range of algorithms. Kitura-CredentialsJWT is a JWT plugin to use with the existing Kitura-Credentials package that offers both Codable and Raw routing methods for easily authenticating JWTs. This guide will demonstrate how to use Swift-JWT and Kitura-CredentialsJWT to implement Single Sign On (SSO) authentication for your Kitura routes. This will allow a user to sign in once and then to access resources from other routes without having to repeat the authentication process.


Step 1: Create the JWT routes

To use JWTs from a server, we need to add Kitura-CredentialsJWT to our dependencies.

If you don't have a server, follow our Create a server guide.

Once we have added Kitura-CredentialsJWT, we need a file for our JWT routes.

Firstly, open your Application.swift file in your default text editor:

open Sources/Application/Application.swift

Inside the postInit() function add:

initializeJWTRoutes(app: self)

Create a new file, called JWTRoutes.swift:

touch Sources/Application/Routes/JWTRoutes.swift

Open your JWTRoutes.swift file:

open Sources/Application/Routes/JWTRoutes.swift

Inside this file, add the following code:

import Foundation
import KituraContracts
import CredentialsJWT

func initializeJWTRoutes(app: App) {
    app.router.post("/jwtlogin") { request, response, next in
        // Read credentials and generate JWT here
        next()
    }
}
extension App {
  // Define JWT signer and verifier here
}

This code imports our required modules, sets up the framework for a routes page and defines the two raw routes that we will use in our guide.


Step 2: Set up your signing and verifying algorithm

Swift-JWT supports multiple algorithms for signing and verifying JWTs as defined by RFC7518. This is implemented by creating a JWTSigner and JWTVerifier struct with a required credentials.

The algorithms are as follows:

Follow one of the links above to configure your signing and verifying algorithm before continuing with the rest of this guide.


Step 3: Define a model to represent the user's credentials

For the initial authentication, the user will have to provide their username and password. This could be achieved with basic authentication, the Authorization header or in the body of a POST request. In this guide we will pass the username and password in the body of a POST request and use a model to represent this.

Passwords and JWTs with sensitive data must be kept private and should always be exchanged over a secure layer like HTTPS.

Create a new file, called UserCredentials.swift:

touch Sources/Application/Models/UserCredentials.swift

Open your UserCredentials.swift file:

open Sources/Application/Models/UserCredentials.swift

Inside this file we define our UserCredentials model:

struct UserCredentials: Codable {
    let username: String
    let password: String
}

Step 4: Authenticate the User

We need to read the user's credentials in our POST route so they can be authenticated.

Inside the POST route add:

let credentials = try request.read(as: UserCredentials.self)
// Users credentials are authenticated

At this stage, you would normally hash the password and verify it against a database. However, for simplicity, we are going to assume the user successfully logged in.


Step 5: Create the signed JWT

A JWT contains claims about the user that we want include in subsequent requests. You can specify any information as a claim, however there are "Registered Claims" which have a pre-defined meaning:

  • iss: The issuer of the token.
  • sub: The subject of the token.
  • aud: The audience of the token.
  • exp: The expiration time which MUST be after the current date/time.
  • nbf: Defines the time before which the JWT MUST NOT be accepted for processing.
  • iat: The time the JWT was issued. Can be used to determine the age of the JWT.
  • jti: Unique identifier for the JWT. Can be used to prevent the JWT from being replayed.

Swift-JWT comes with a struct representing these Registered Claims which we will use for our example.

Inside the POST route, beneath the code where we authenticated the user, initialize the user's claims:

let myClaims = ClaimsStandardJWT(iss: "Kitura", sub: credentials.username, exp: Date(timeIntervalSinceNow: 3600))

The claims information tells us the username which is the subject of the token, that they were authenticated by Kitura and that the token will expire in one hour.

Next, we will initialize our JWT:

var myJWT = JWT(claims: myClaims)

We can sign this JWT using the JWTSigner we created in step 2:

let signedJWT = try myJWT.sign(using: App.jwtSigner)

Finally we return the signed JWT string to the user:

response.send(signedJWT)

Our completed login route should look as follows:

app.router.post("/jwtlogin") { request, response, next in
    let credentials = try request.read(as: UserCredentials.self)
    // Users credentials are authenticated
    let myClaims = ClaimsStandardJWT(iss: "Kitura", sub: credentials.username, exp: Date(timeIntervalSinceNow: 3600))
    var myJWT = JWT(claims: myClaims)
    let signedJWT = try myJWT.sign(using: App.jwtSigner)
    response.send(signedJWT)
    next()
}

Step 6: Test the JWT creation

Compile your project and start the server.

To test the route using curl, open Terminal and enter the following:

curl -X POST \
    http://localhost:8080/jwtlogin \
    -H 'content-type: application/json' \
    -d '{
    "username": "Joe Bloggs",
    "password": "password"
}'

You should be returned a JWT string that is structured xxxx.yyyy.zzzz where xxxx is the base64 encoded header, yyyy is the base 64 encoded claims and zzzz is the signature.

Below is an example JWT, generated using HS256 with the password "kitura". The one returned by your curl request will have different values but the same structure.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJLaXR1cmEiLCJzdWIiOiJKb2UgQmxvZ2dzIiwiZXhwIjoxNTUzMDE4Mjg0LjMyOTcwMTl9.t55WealACtYGCQGS3EQgRQuurmNSBO5fWZqzqJjEIi

We can decode the JWT string using the debugger at jwt.io which allows us view the headers and claims.


Step 7: Verify a JWT

So far, we have created a signed JWT, which allows a user to authenticate themselves. At this stage, the user would attach the JWT string to future requests either using cookies or the Authorization header. When we receive this JWT string on other routes, we need to verify that we signed it and it hasn't been altered.

From here we will use the CredentialsJWT plugin for authenticating the user with the earlier received token.

After the POST function, add the following to the file:

let jwtCredentials = CredentialsJWT<ClaimsStandardJWT>(verifier: App.jwtVerifier)
let authenticationMiddleware = Credentials()
authenticationMiddleware.register(plugin: jwtCredentials)
app.router.get("/jwtprotected", middleware: authenticationMiddleware)
app.router.get("/jwtprotected") { request, response, next in
        guard let userProfile = request.userProfile else {
                Log.verbose("Failed raw token authentication")
                response.status(.unauthorized)
                try response.end()
                return
            }
        response.send("\(userProfile.id)\n")
            next()
        }

Let's break these lines down individually. The first line creates a CredentialsJWT instance with the default options using the built in ClaimsStandardJWT claims from the Swift-JWT package. The second line creates the middleware instance using the Credentials package that we can register plugins to. The third line registers the created CredentialsJWT instance to the created middleware instance and the line after adds a GET route to the router that allows an authentication request to take place. The final line is the declaration of the function that will verify the JWT we send, if the request is authorized, then the response sent is the id field of your JWT.


Step 8: Test the protected Route

To test this, restart your server and send the POST request from Step 6.

Copy the returned JWT string and paste it into the following curl request:

curl -X GET \
http://localhost:8080/jwtprotected \
-H 'X-Token-Type: JWT' \
-H 'Authorization: Bearer <Your JWT string here>'

You should see your username returned to you. This should look something like:

Joe Bloggs

Congratulations! We have just created a JWT single sign on system using a Kitura Server. Your completed JWTRoutes.swift file for HS256 should look as follows:

import Foundation
import KituraContracts
import CredentialsJWT
import SwiftJWT
import Credentials
import LoggerAPI

func initializeJWTRoutes(app: App) {

    app.router.post("/jwtlogin") { request, response, next in
        let credentials = try request.read(as: UserCredentials.self)
        // Users credentials are authenticated
        let myClaims = ClaimsStandardJWT(iss: "Kitura", sub: credentials.username, exp: Date(timeIntervalSinceNow: 3600))
        var myJWT = JWT(claims: myClaims)
        let signedJWT = try myJWT.sign(using: App.jwtSigner)
        response.send(signedJWT + "\n")
        next()
    }

    let jwtCredentials = CredentialsJWT<ClaimsStandardJWT>(verifier: App.jwtVerifier)
    let authenticationMiddleware = Credentials()
    authenticationMiddleware.register(plugin: jwtCredentials)
    app.router.get("/jwtprotected", middleware: authenticationMiddleware)
    app.router.get("/jwtprotected") { request, response, next in
        guard let userProfile = request.userProfile else {
                Log.verbose("Failed raw token authentication")
                response.status(.unauthorized)
                try response.end()
                return
            }
        response.send("\(userProfile.id)\n")
            next()
        }
}

extension App {
    // Define JWT signer and verifier here
    static let jwtSigner = JWTSigner.hs256(key: Data("kitura".utf8))
    static let jwtVerifier = JWTVerifier.hs256(key: Data("kitura".utf8))
}

Step 9: Using custom claims (Optional)

You may want to use your own set of custom claims for your JWT. For this to work, we need to specify the subject and UserProfileDelegate options.

First we will create our claims structure, in our JWTRoutes.swift file:

struct MyClaims: Claims {
  let id: String
  let fullName: String
  let email: String
}

Now we need to edit our UserCredentials model to contain these additional values, so go into your UserCredentials.swift file and add these values to your model:

struct UserCredentials: Codable {
    let username: String
    let password: String
    let email: String
    let fullName: String
}

For simplicity, in this example, the id will have the same value as the username.

We need to rewrite our JWT generation so that it creates a JWT with the correct claims:

app.router.post("/jwtlogin") { request, response, next in
    let credentials = try request.read(as: UserCredentials.self)
    // Users credentials are authenticated
    let myClaims = MyClaims(id: credentials.username, fullName: credentials.fullName, email: credentials.email)
    var myJWT = JWT(claims: myClaims)
    let signedJWT = try myJWT.sign(using: App.jwtSigner)
    response.send(signedJWT)
    next()
}

Let's test our newly created route!

curl -X POST \
    http://localhost:8080/jwtlogin \
    -H 'content-type: application/json' \
    -d '{
    "username": "JoeBloggs312",
    "password": "password",
    "email": "joebloggs@email.com",
    "fullName": "Joe Kitura Bloggs"
}'

None of these claims are part of the ClaimsStandardJWT claims and the subject claim is not present, therefore we need to use the UserProfileDelegate and update it with our custom claims.

After our POST route, add:

struct MyDelegate: UserProfileDelegate {
    func update(userProfile: UserProfile, from dictionary: [String:Any]) {
        // `userProfile.id` already contains `id`
        userProfile.displayName = dictionary["fullName"]! as! String
        let email = UserProfile.UserProfileEmail(value: dictionary["email"]! as! String, type: "home")
        userProfile.emails = [email]
    }
}

Then we need to declare our instance of CredentialsJWT with our newly defined options, as well as setting the subject claim to id:

let jwtCredentials = CredentialsJWT<MyClaims>(verifier: App.jwtVerifier, options: [CredentialsJWTOptions.subject: "id", CredentialsJWTOptions.userProfileDelegate: MyDelegate()])

Finally we create our middleware and register our plugin to it, the same as we have done earlier:

let authenticationMiddleware = Credentials()
authenticationMiddleware.register(plugin: jwtCredentials)
app.router.get("/jwtprotected", middleware: authenticationMiddleware)
app.router.get("/jwtprotected") { request, response, next in
    guard let userProfile = request.userProfile else {
            Log.verbose("Failed raw token authentication")
            response.status(.unauthorized)
            try response.end()
            return
        }
    response.send("\(userProfile.id)\n")
        next()
    }

Using the same terminal commands as earlier, you can test your JWT generation and authentication:

curl -X GET \
http://localhost:8080/jwtprotected \
-H 'X-Token-Type: JWT' \
-H 'Authorization: Bearer <Your JWT string here>'

Congratulations! You are now using your own set of custom claims to generate a JWT and authenticate a user!

Step 10: JWTs on Codable Routes (Optional)

In our example we used raw routing since we chose to pass the user credentials via the request headers. If we want to use JWTs on our codable routes, we need to encapsulate the verification and creation of the users JWT in a TypeSafeMiddleware.

Step 10a: Register TypeSafeJWT on a route.

In the JWTRoutesFile.swift we are going to create a new route for a TypeSafeJWT.

In the function initializeJWTRoutes, declare the verifier for the TypeSafeJWT (this example is using the HS256 algorithm):

TypeSafeJWT.verifier = .hs256(key: Data("kitura".utf8))

We will then add the codable route handler, the handler will only be invoked if the JWT can be successfully verified, and contains the required claims.

app.router.get("/jwtcodable") {  (jwt: JWT<ClaimsStandardJWT>, respondWith: (JWT<ClaimsStandardJWT>?, RequestError?) -> Void) in
     respondWith(jwt, nil)
}

Step 10b: Test the new Codable route

To test this route, restart your server and send the POST request from Step 6.

Copy the returned JWT string and paste it into the following curl request:

curl -X GET \
http://localhost:8080/jwtcodable \
-H 'X-Token-Type: JWT' \
-H 'Authorization: Bearer <Your JWT string here>'

You should see your JWT claims returned to you. This should look something like:

{"claims":{"iss":"Kitura","sub":"Joe Bloggs","exp":574703307.61258602},"header":{"typ":"JWT","alg":"HS256"}}