Bartek Andrzejczak

Musings on software development

SSO for Your Single Page Application (Part 2/2 - Akka HTTP)

In the previous post I’ve shown you how to integrate front-end part of our application with Keycloak as a Single Sign-on server. That is really important, but almost no real application can work without a back-end. That’s why today we’ll delve into the problem of authorizing requests in the server application. If we’re using an application container this job will probably be much easier. Keycloak provides some out of the box adapters for most mainstream servers, so the only thing you have to do is to download them and add a little bit of configuration. That’s a piece of cake. That’s why we’ll look into something different: adding OAuth2 and in particular Keycloak support for Akka HTTP.

Preparations

I want to show you the code as soon as possible, so let me just give you the minimal introduction you need.

As for Keycloak we don’t really need another client besides the one created in the previous post. Normally we would create a bearer-only client, so we can actually differentiate between authorization requests made from the front-end and from the back-end, but that’s not necessary for it to work right now.

As for the application minimal set of dependencies, let’s look at build.sbt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
name := "keycloak-akka-http"

version := "1.0"

scalaVersion := "2.11.7"

val akkaV = "2.4.1"
val akkaStreamV = "2.0-M2"

val akka = Seq (
  "com.typesafe.akka" %% "akka-actor" % akkaV,
  "com.typesafe.akka" %% "akka-http-experimental" % akkaStreamV,
  "com.typesafe.akka" %% "akka-http-spray-json-experimental" % akkaStreamV
)

val logging = Seq (
  "org.slf4j" % "slf4j-api" % "1.7.12",
  "ch.qos.logback" % "logback-classic" % "1.1.3",
  "com.typesafe.scala-logging" %% "scala-logging" % "3.1.0",
  "com.typesafe.akka" %% "akka-slf4j" % akkaV
)

val keycloak = Seq (
  "org.keycloak" % "keycloak-adapter-core" % "1.6.1.Final",
  // we include all necessary transitive dependencies,
  // because they're marked provided in keycloak pom.xml
  "org.keycloak" % "keycloak-core" % "1.6.1.Final",
  "org.jboss.logging" % "jboss-logging" % "3.3.0.Final",
  "org.apache.httpcomponents" % "httpclient" % "4.5.1"
)

libraryDependencies ++= akka ++ logging ++ keycloak

Dependencies are divided into three groups:

  • akka - Akka-related dependencies. We’ll need base akka library to create an Actor system needed for akka-http. We’ll also need spray-json for akka-http to send some JSON data back to the client.
  • logging - Logging-related dependencies. Nothing special here.
  • keycloak - We’re using keycloak-adapter-core to validate our requests. The problem is, that this library is build mainly with application containers in mind, so we need to fetch some of it’s dependencies by hand.

Token verifier

It looks like we’ve got our setup ready. Let’s go straight into the meat. The first thing we’ll need is something to actually verify if the given token is valid in a provided Keycloak realm. To make it more general and reusable let’s use a trait:

1
2
3
trait TokenVerifier {
  def verifyToken(token: String): Future[String]
}

For the purpose of this tutorial let’s say, that verifyToken method will accept a token an return either a Success with user’s preffered username inside or a Failure if authentication failed for any reason. The authentication may take some time, so we’ll use Future here not to block the application.

Because we’re using Keycloak, we’ll need a Keycloak-specific implementation of this trait. Here is is:

1
2
3
4
5
6
7
8
9
10
11
12
13
class KeycloakTokenVerifier(keycloakDeployment: KeycloakDeployment) extends TokenVerifier {
  implicit val executionContext = ExecutionContext.fromExecutor(new ForkJoinPool(2))

  def verifyToken(token: String): Future[String] = {
    Future {
      RSATokenVerifier.verifyToken(
        token,
        keycloakDeployment.getRealmKey,
        keycloakDeployment.getRealmInfoUrl
      ).getPreferredUsername
    }
  }
}

The class accepts as its constructor an instance of KeycloakDeployment class which is a part of keycloak-core library. It will tell us where to look for a Keycloak instance. Implementation of verifyToken method is pretty straightforward. We use RSATokenVerifier provided by keycloak-adapter-core which will first check if the token has a valid structure and then send a request to Keycloak to ask whether this token is valid in the application realm. This method will return an AccessToken instance, from which we can extract preferred username. All of this is wrapped into Future.apply method which will take care of asynchronous computation and authorization failures communicated by exceptions.

Extracting token value

With that in place we can go into integrating it with Akka HTTP routing mechanism. It is build on the concept of directives which either fit the supplied request or reject it (which is a simplification, but you get the gist). They can also provide some values. We’d like to have a directive of our own, that would fit the request only if it carries valid authentication token, and that would provide instance of OAuth2Token class consisting of token text and preferred username.

First we’ll need a directive that will fit only requests that carry some authentication token. It would be good if the token could be sent as either header or a cookie. (we’ll tackle this header vs. cookie debate later). Here’s the code for this token extraction directive:

1
2
3
4
5
6
7
8
9
10
private def bearerToken: Directive1[Option[String]] =
 for {
   authBearerHeader  <- optionalHeaderValueByType(classOf[Authorization]).map(extractBearerToken)
   xAuthCookie       <- optionalCookie("X-Authorization-Token").map(_.map(_.value))
 } yield authBearerHeader.orElse(xAuthCookie)

private def extractBearerToken(authHeader: Option[Authorization]): Option[String] =
 authHeader.collect {
   case Authorization(OAuth2BearerToken(token)) => token
 }

optionalHeaderValueByType and optionalCookie are both methods comming from akka-http and they both return an Directive1[Option[_]] instance. We need to unify it to Directive1[Option[String]] containing token value.

In case of cookie it’s pretty straightforward. We need to map the directive to get to it’s value which is of type Option[HttpCookiePair], which we map again extracting its String value.

For header it’s a bit more complicated. Required Authorization header will have Bearer {token} format. We could either extract the token value with a Regexp matcher or use extractors provided by akka-http. Using a regexp to solve a problem is creating another two, so we’ll go with extractors here. OAuth2BearerToken will do a job for us, giving us String token ready to use.

Last thing that’s left to do is actually joining those two directives into one. Here we’ll go with auth header before a cookie, but we don’t really consider having both the cookie and the header on one request.

Validating request token

Second directive we need to write is directive that will verify the token if it exists on the request and reject the request if it doesn’t. Here it goes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def authorized: Directive1[OAuth2Token] = {
  bearerToken.flatMap {
    case Some(token) =>
      onComplete(tokenVerifier.verifyToken(token)).flatMap {
        _.map(username => provide(OAuth2Token(token, username)))
          .recover {
            case ex =>
              logger.error(ex, "Couldn't log in using provided authorization token")
              reject(AuthorizationFailedRejection).toDirective[Tuple1[OAuth2Token]]
          }
          .get
      }
    case None =>
      reject(AuthorizationFailedRejection)
  }
}

If the token is part of the request in either header of cookie form, we verify it with tokenVerifier passed to the class as a parameter. Verification is asynchronous, so we need to wait for it to complete using another one of Akka HTTP directives: onComplete. When we’ve got the result of the verification it will be of type Try[String]. Its successful value can be mapped as a provide directive that always fits the requests and provides a given value. If the verification failed we recover by logging the error and rejecting the request. We need to use toDirective explicitly (instead of default implicit conversion) because the type inference doesn’t get that one. After we’ve recovered from any exceptions it’s safe to unwrap Try with get. If there’s no token on the request, we simply reject it.

Putting it all together

How to use what we’ve done so far? Just like we’d use any other directive! Let’s look at the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
implicit val system = ActorSystem("sso-system")
implicit val actorMaterializer = ActorMaterializer()

val oauth2 = new OAuth2Authorization(
  Logging(system, getClass),
  new KeycloakTokenVerifier(
    KeycloakDeploymentBuilder.build(
      getClass.getResourceAsStream("/keycloak.json")
    )
  )
)
import oauth2._

val routes = authorized { token =>
  path("test") {
    get {
      jsonpWithParameter("callback") {
        complete(token)
      }
    }
  }
}

Http().bindAndHandle(routes, "localhost", 9000)

Since we’re using Akka HTTP we’ll need both implicit ActorSystem and ActorMaterializer instances in place. Then we can create instance of our OAuth2Authorization class, that holds authorize directive. We’ll use it with akka.event.Logging and our KeycloakTokenVerifier. KeycloakDeploymentBuilder will be build using keycloak.json saved in the resources folder. We’ve acquired this file in the course of previous tutorial. When we have our authorization object created we can import its contents for the better feels of the API. No we can simply use it writing:

1
2
3
authorized { token =>
  // the part of the API that we need to be checked for authorization
}

The last line creates an HTTP server and binds our routing (transforming it into Flow with help of ActorMaterializer) to a chosen interface and port. And that’s it!

Testing it with AngularJS

How do we know that it actually works? Let’s put it to test by extending our AngularJS application from the previous post to make an HTTP request to the back-end.

First we need to store a token acquired in the process of logging in to use it in later requests. We’ll do it in the run function defined before:

1
$cookies.put('X-Authorization-Token', keycloak.token);

We add a cookie, that will be added to all the requests made to the server.

Keycloak token have some expiration date, so we’ll need to cover that one by refreshing it from time to time:

1
2
3
4
5
6
7
8
9
var updateTokenInterval = $interval(function () {
  // refresh token if it's valid for less then 15 minutes
  keycloak.updateToken(15)
    .success(function (refreshed) {
      if (refreshed) {
        $cookies.put('X-Authorization-Token', keycloak.token);
      }
    });
}, 10000);

When the user loggs out we’ll also need to reset this interval and clear the cookie:

1
2
3
4
5
$rootScope.userLogout = function () {
  $cookies.remove('X-Authorization-Token');
  $interval.cancel(updateTokenInterval);
  keycloak.logout();
};

Now we’re prepared to make a request to the server. A little digression here: Since we’re binding client and server applications on a different port, we’d normally have to deal with CORS. To avoid it here we will use JSONP request, which is a regular GET request, which expects, that data coming back from the server will be wrapped in a JavaScript function. Angular will then call this function and retrieve the result. This is why in the line 17 of our main application class we used jsonpWithParameter directive. This directive was once a part of spray which is an ancestor of Akka HTTP, but then went missing. That’s why I had to reimplement it for Akka HTTP here.

Going back on tracks, here’s the code for making the request:

1
2
3
4
5
6
7
$rootScope.authData = {};

$http.jsonp("http://localhost:9000/test?callback=JSON_CALLBACK")
    .success(function (response) {
        $rootScope.authData.token = response.token;
        $rootScope.authData.username = response.username;
    });

JSON_CALLBACK will be replaced by angular with a name of actual method that it uses and the request will be executed as GET. Now believe me or not, but having Keycloak, Server and Client applications up, this actually works!

If you still don’t believe me, I encourage you to download the source code and see it for yourselves :)

Addendum: Cookies vs. Headers

In this post I’ve used cookies as a mean of storing authorization token. Why the demonized cookies over safe headers? The reason is quite simple. For one thing, I wanted to show you, that it can be done either way - the server can accept both headers and cookies and it will be fine. The other thing is, that even though the cookies are less safe, they work where headers don’t. One such case is image fetching. We could’ve done it with some custom angular directive that downloads image with a simple GET request with the header set and then creates <img> element out of it, but if you want to be able to write <img src="host/someImage" />, you’re basically left with a cookie, because that request will be made by a browser, which doesn’t care about default headers for AngularJS, but it definitely cares for cookies set. That’s all ;)

As usual, the source code is available on GitHub: https://github.com/bandrzejczak/keycloak-angular-akka-http.

Comments