Dependencies

First, create a new Gradle project with the following dependencies: (→ Tutorial)

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
    compile "io.javalin:javalin:2.4.0"
    compile "com.fasterxml.jackson.module:jackson-module-kotlin:2.8.4"
    compile "org.slf4j:slf4j-simple:1.7.25"
}

Creating controllers

We need something worth protecting. Let’s pretend we have a very important API for manipulating a user database. We make a controller-object with some dummy data and CRUD operations:

import io.javalin.Context
import java.util.*

object UserController {

    private data class User(val name: String, val email: String)

    private val users = hashMapOf(
            randomId() to User(name = "Alice", email = "[email protected]"),
            randomId() to User(name = "Bob", email = "[email protected]"),
            randomId() to User(name = "Carol", email = "[email protected]"),
            randomId() to User(name = "Dave", email = "[email protected]")
    )

    fun getAllUserIds(ctx: Context) {
        ctx.json(users.keys)
    }

    fun createUser(ctx: Context) {
        users[randomId()] = ctx.body<User>()
    }

    fun getUser(ctx: Context) {
        ctx.json(users[ctx.pathParam(":user-id")]!!)
    }

    fun updateUser(ctx: Context) {
        users[ctx.pathParam(":user-id")!!] = ctx.body<User>()
    }

    fun deleteUser(ctx: Context) {
        users.remove(ctx.pathParam(":user-id"))
    }

    private fun randomId() = UUID.randomUUID().toString()

}

We’re using !! to convert nullables to non-nullables. If :user-id is missing or users[id] returns null, we’ll get a NullPointerException and our application will crash. Handling this is outside the scope of the tutorial.

Creating roles

Now that we have our functionality, we need to define a set of roles for our system. This is done by implementing the Role interface from io.javalin.security.Role. We’ll define three roles, one for “anyone”, one for permission to read user-data, and one for permission to write user-data.

enum class ApiRole : Role { ANYONE, USER_READ, USER_WRITE }

Setting up the API

Now that we have roles, we can implement our endpoints:

import io.javalin.ApiBuilder.*
import io.javalin.Javalin
import io.javalin.security.Role.roles

fun main(vararg args: String) {

    val app = Javalin.create().apply {
        accessManager(Auth::accessManager)
    }.start()

    app.routes {
        path("users") {
            get(UserController::getAllUserIds, roles(ApiRole.ANYONE))
            post(UserController::createUser, roles(ApiRole.USER_WRITE))
            path(":user-id") {
                get(UserController::getUser, roles(ApiRole.USER_READ))
                patch(UserController::updateUser, roles(ApiRole.USER_WRITE))
                delete(UserController::deleteUser, roles(ApiRole.USER_WRITE))
            }
        }
    }

}

A role has now been given to every endpoint:

  • ANYONE can getAllUserIds
  • USER_READ can getUser
  • USER_WRITE can createUser, updateUser and deleteUser

Now, all that remains is to implement the access-manager (Auth::accessManager).

Implementing auth

The AccessManager interface in Javalin is pretty simple. It takes a Handler a Context and a list of Roles. The idea is that you implement code to run the handler based on what’s in the context, and what roles are set for the endpoint.

The rules for our access manager are also simple:

  • When endpoint has ApiRole.ANYONE, all requests will be handled
  • When endpoint has another role set and the request has matching credentials, the request will be handled
  • Else we ignore the request and send 401 Unauthorized back to the client

This translates nicely into Kotlin:

fun accessManager(handler: Handler, ctx: Context, permittedRoles: List<Role>) {
    when {
        permittedRoles.contains(ApiRole.ANYONE) -> handler.handle(ctx)
        ctx.userRoles.any { it in permittedRoles } -> handler.handle(ctx)
        else -> ctx.status(401).json("Unauthorized")
    }
}

Extracting user-roles from the context

There is no ctx.userRoles concept in Javalin, so we need to implement it. First we need a user-table. We’ll create a map(Pair<String, String>, List<Role>) where keys are username+password in cleartext (please don’t do this for a real service), and values are user-roles:

private val userRoleMap = hashMapOf(
        Pair("alice", "weak-password") to listOf(ApiRole.USER_READ),
        Pair("bob", "better-password") to listOf(ApiRole.USER_READ, ApiRole.USER_WRITE)
)

Now that we have a user-table, we need to authenticate the requests. We do this by getting the username+password from the Basic-auth-header and using them as keys for the userRoleMap:

private val Context.userRoles: List<ApiRole>
    get() = this.basicAuthCredentials()?.let { (username, password) ->
        userRoleMap[Pair(username, password)] ?: listOf()
    } ?: listOf()

When using basic auth, credentials are transferred as plain text (although base64-encoded). Remember to enable SSL if you’re using basic-auth for a real service.

Conclusion

This tutorial showed one possible way of implementing an AccessManager in Javalin, but the interface is very flexible and you can really do whatever you want:

app.accessManager(handler, ctx, permittedRoles) -> {
    when {
        ctx.host().contains("localhost") -> handler.handle(ctx)
        Math.random() > 0.5 -> handler.handle(ctx)
        dayOfWeek == Calendar.SUNDAY -> handler.handle(ctx)
        else -> ctx.status(401).json("Unauthorized")
    }
};