Kitura Logo

TYPE-SAFE HTTP BASIC AUTHENTICATION

Introduction

Kitura 2.4 introduces "Type-safe Middleware": middlewares with the structure and data types that you define. On successfully executing, they are instantiated and passed to your handler. We also introduce TypeSafeCredentials, a protocol that implements TypeSafeMiddleware to authenticate a request on a Codable route and initialize itself with the authenticated user's data.

HTTP Basic authentication transmits credentials in an "Authorization" header as base64 encoded user ID/password pairs. Many clients also let you send the username and the password in the URL as follows:

https://username:password@www.example.com/

However some web browsers disable this for security reasons.

This guide steps you through using TypeSafeHTTPBasic, a TypeSafeCredentials plugin for safe and easy HTTP basic authentication in Codable routes.

Adding TypeSafeHTTPBasic to your project

If you do not have an existing project, run kitura init in terminal to generate a new project.

Add Kitura-CredentialsHTTP to your Package.swift dependencies:

.package(url: "https://github.com/IBM-Swift/Kitura-CredentialsHTTP.git", from: "2.1.0"),

Add CredentialsHTTP to your Application targets:

.target(name: "Application", dependencies: [ "CredentialsHTTP", ...

Regenerate your Xcode project:

swift package generate-xcodeproj

Open your Xcode project and go to Sources >> Application >> Application.swift

Add CredentialsHTTP to your import statements:

import CredentialsHTTP

Defining a TypeSafeHTTPBasic object

We will declare a struct which conforms to TypeSafeHTTPBasic. This will be initialized when our route is successfully authenticated and we will be able to access the authenticated user's id within our Codable route.

Define the TypeSafeHTTPBasic Struct:

At the bottom of Application.swift, define a public struct called MyBasicAuth that conforms to the TypeSafeHTTPBasic protocol:

public struct MyBasicAuth: TypeSafeHTTPBasic {

}

Add protocol stubs:

Xcode should display the message: Type 'MyBasicAuth' does not conform to protocol 'TypeSafeCredentials'

Click "Fix" to autogenerate the stubs and produce the struct below:

public struct MyBasicAuth: TypeSafeHTTPBasic {
    public static func verifyPassword(username: String, password: String, callback: @escaping (MyBasicAuth?) -> Void) {

    }

    public var id: String
}

Inside MyBasicAuth, add an authentication dictionary:

public static let authenticate = ["username" : "password"]

Note: In a real project, never store passwords in plain text!

Define verifyPassword:

The function, verifyPassword, takes a username and password and, on success, returns a MyBasicAuth instance . We want to check if the password matches the user's stored password. On successful match, we initialize MyBasicAuth with an id equal to username.

if let storedPassword = authenticate[username], storedPassword == password {
    callback(MyBasicAuth(id: username))
    return
}
callback(nil)

This function is asyc, so that you can perform asyc actions to verify the password, e.g. looking up the username and password in a database. You must call the callback closure with either an instance of Self or nil before exiting verifyPassword. If you do not, the server will not know to continue and you will recieve a 503 "Service Unavailable" error, when you call the route.

Your complete struct should now look as follows:

public struct MyBasicAuth: TypeSafeHTTPBasic {

    public static let authenticate = ["username" : "password"]

    public static func verifyPassword(username: String, password: String, callback: @escaping (MyBasicAuth?) -> Void) {
        if let storedPassword = authenticate[username], storedPassword == password {
            callback(MyBasicAuth(id: username))
            return
        }
        callback(nil)
    }

    public var id: String
}

Using TypeSafeHTTPBasic in a route

Inside postInit(), add the following route:

router.get("/basic") { (user: MyBasicAuth, respondWith: (MyBasicAuth?, RequestError?) -> Void) in
    print("authenticated \(user.id) using \(user.provider)")
    respondWith(user, nil)
}

Start your Kitura server

Go to http://localhost:8080/basic

Login with User Name: username, Password: password

You will successfully login and be returned your username

Congratulations, you have just implemented HTTP basic authentication of a Codable route!!!

The browser will store your login credentials and automatically log you in if you return to the route. Use a private window if you would like to test incorrect authentication.

Using TypeSafeHTTPBasic with the ORM

This example showed a simple struct that contains only the required id. A real world project would probably have a user profile with lots of fields, which is keyed by the unique id. We demonstrate how to implement this using Swift-Kuery-ORM.

Set up the ORM and connect it to a database by following the instructions in the README using MyBasicAuth as your Model.

Make MyBasicAuth a Model:

public struct MyBasicAuth: TypeSafeHTTPBasic, Model {

Inside MyBasicAuth, set id as the id column in the database

static var idColumnName = "id"

Remove public static let authenticate = ["username" : "password"]

Add in a new fields for the password and a custom field:

private let password: String
public let customField: Int

Change verifyPassword to initialize MyBasicAuth from the database:

MyBasicAuth.find(id: username) { userProfile, error in
    if let userProfile = userProfile {
        if password == userProfile.password {
            callback(userProfile)
            return
        }
    }
    callback(nil)
}

MyBasicAuth should now look as follows:

public struct MyBasicAuth: TypeSafeHTTPBasic, Model {

    static var idColumnName = "id"

    public static func verifyPassword(username: String, password: String, callback: @escaping (MyBasicAuth?) -> Void) {
        MyBasicAuth.find(id: username) { userProfile, error in
            if let userProfile = userProfile {
                if password == userProfile.password {
                    callback(userProfile)
                    return
                }
            }
        callback(nil)
    }

    public var id: String
    private let password: String
    public let customField: Int
}

Now when your route authenticates, a MyBasicAuth instance with a primary key matching the provided user name is returned. The instance fields on MyBasicAuth should match your user data giving you type-safe authentication in your Codable routes!

Slack icon

NEED HELP?

MESSAGE US ON SLACK.