TL;DR: In this article we're using to use Scala and the Play Framework to build an API that serves blog posts and comments, and then secure the API using access tokens. We will build the API from scratch over the course of this article, but if you'd like to see the final result, you can find the sample code on GitHub.com.
Let's build an API using Scala and the Play Framework! This API is going to be fairly simple. It will have two endpoints — one for serving blog posts, and one for serving comments — that we can then secure using access tokens. That is, clients without a valid access token will not be able to access the blog data.
We'll start by building out the API, and then later we'll use Auth0 to issue our access tokens. We'll then validate these tokens and decide whether or not the connected client should access the requested resource or not.
Prerequisites
To follow along with this article, you will want to have the following software installed:
- SBT
- built using Scala for Scala projects, this is the CLI that enables you to compile and run Scala programs
- An appropriate editor or IDE. I used IntelliJ IDEA to build this tutorial. They have a free community edition if you want to do the same, and don't already have an IDE
Note IntelliJ requires the Scala plugin to be installed in order to work with Scala projects. To install the plugin, read the first section "Install Scala plugin" on the IntelliJ website. If you are using a different IDE such as Eclipse or Netbeans, please check to see if you need to install any plugins for Scala development.
In addition, you should have a basic working knowledge of Scala and some of its basic types and control flows, as we'll not be covering them in detail here. If you're new to Scala, you might be better taking the tour of Scala before returning to complete this tutorial.
Setting Up the Project
To begin, find a place on your hard-drive using the terminal to start a new project. We're going to be initializing our project using SBT and Giter8, which is a templating CLI that is used from the SBT console and can load project templates from GitHub. Play Framework has a set of Giter8 templates that we can use to kick-start our project without using an IDE.
Scaffold the project in the terminal now, using the following command. As part of the running of this command, you will be asked for a project name. A folder will be created with this name, and your project code will be placed inside:
sbt new playframework/play-scala-seed.g8
You will also be asked for an organization name. I left mine as the default of
com.example
.Next, open the project in your editor of choice, or use the 'import' feature if you're using IntelliJ. If you are given a choice as to which build tool you'd like to use, make sure you select 'sbt'.
Before we dive into the project code, we're going to clean up the default project a little bit and remove the stuff we don't need. You can do this by removing the controllers, views, and public assets that we're not going to use:
# Remove unused folders rm -rf app/controllers/HomeController.scala app/views public test # Clear out the routes, as they'll no longer compile echo "" > conf/routes
Now you should have a nice, clean, blank template on which to build our API.
“Learn how to build a Scala Play Framework API and secure it with access tokens”
Tweet This
Building the API
We're going to start by getting a basic controller up and running that will host our API endpoints. Then, we'll create some models specific to the domain we're working with, before going back to the controller and creating our blog and comments endpoints. That way we can test the working of the controller incrementally as we go.
Starting our API controller
To start, create a new file in the
app/controllers
folder called ApiController.scala
:touch app/controllers/ApiController.scala
Next, populate
ApiController.scala
with the following:// app/controllers/ApiController.scala // Make sure it's in the 'controllers' package package controllers import javax.inject.{Inject, Singleton} import play.api.mvc.{AbstractController, ControllerComponents} @Singleton class ApiController @Inject()(cc: ControllerComponents) extends AbstractController(cc) { // Create a simple 'ping' endpoint for now, so that we // can get up and running with a basic implementation def ping = Action { implicit request => Ok("Hello, Scala!") } }
Next, open the
conf/routes
file and enter in a route that enables requests to be made to our ping
endpoint:GET /api/ping controllers.ApiController.ping
With this basic implementation in place, we should be able to run the project and make a request to our simple
ping
endpoint. Switch back to your terminal and start the project using the SBT CLI.sbt run 9000
Note: I've chosen to start mine on port 9000, but which port number you choose does not matter. Also, as it's the first time we're running this command, it might take a few minutes for all the dependencies to be downloaded. However, running the project after this initial step should be much faster.
To give it a test run, open another terminal window and use the
curl
command to issue a request to our ping
endpoint:curl localhost:9000/api/ping
If everything is working correctly, you should see the words "Hello, Scala!" appear in the terminal!
If you like, you can leave the application running, as SBT will pick up file changes and recompile automatically.
Adding models and data
Let's add some types and a data repository more relevant to our application — an API that serves blog posts and comments.
We'll add:
- a
model, representing a single blog postPost
- a
model, representing a single comment on a blog postComment
- a
type, which statically provides blogs and comments for the purposes of testing the APIDataRepository
Start by creating a new folder inside
app
called models
, and create a new file in there called Post.scala
:# You can use this in your terminal to create the folder and add the file mkdir app/models && touch app/models/Post.scala
Then, open
Post.scala
and populate it with the following:// app/models/Post.scala // Make sure it goes in the models package package models import play.api.libs.json.Json // Create our Post type as a standard case class case class Post(id: Int, content: String) object Post { // We're going to be serving this type as JSON, so specify a // default Json formatter for our Post type here implicit val format = Json.format[Post] }
Here we have a case class to represent our Post data. For simplicity's sake, it only has two properties:
id
, and content
. You'll see that we've specified a JSON formatter. This is used by the Play Framework when it comes to sending our Post
object back to the client as JSON. It is also able to go the other way and rehydrate our Post
model from JSON. This is convenient, as rather than manually specify what the exact shape of the JSON is, we can use this built-in formatter which generates the JSON based on the properties and types that are present on the model. Play Framework has good documentation on working with JSON if you would like to read further into how JSON formatting works.Now that we've got our
Post
model implemented, we can implement our Comment
model in a very similar way. Create a new file inside app/models
called Comment.scala
:touch app/models/Comment.scala
Next, open
Comment.scala
and populate it with the following:// app/models/Comment.scala package models import play.api.libs.json.Json // Represents a comment on a blog post case class Comment(id: Int, postId: Int, text: String, authorName: String) object Comment { // Use a default JSON formatter for the Comment type implicit val format = Json.format[Comment] }
To complete our data story, create a new folder
app/repositories
and a new file within called DataRepository.scala
:mkdir app/repositories && touch app/repositories/DataRepository.scala
Open
DataRepository.scala
and add in the following:// app/repositories/DataRepository.scala package repositories import javax.inject.Singleton import models.{Comment, Post} @Singleton class DataRepository { // Specify a couple of posts for our API to serve up private val posts = Seq( Post(1, "This is a blog post"), Post(2, "Another blog post with awesome content") ) // Specify some comments for our API to serve up private val comments = Seq( Comment(1, 1, "This is an awesome blog post", "Fantastic Mr Fox"), Comment(2, 1, "Thanks for the insights", "Jane Doe"), Comment(3, 2, "Great, thanks for this post", "Joe Bloggs") ) /* * Returns a blog post that matches the specified id, or None if no * post was found (collectFirst returns None if the function is undefined for the * given post id) */ def getPost(postId: Int): Option[Post] = posts.collectFirst { case p if p.id == postId => p } /* * Returns the comments for a blog post * If no comments exist for the specified post id, an empty sequence is returned * by virtue of the fact that we're using 'collect' */ def getComments(postId: Int): Seq[Comment] = comments.collect { case c if c.postId == postId => c } }
This is a simple implementation of an in-memory data store that our API can make use of in order to return some useful data to the client.
Revisiting our API controller
Let's go back to our
ApiController.scala
file and pull in the repository we just created. We can do this by using Play Framework's Dependency Injection feature. All we have to do is specify DataRepository
as a constructor argument on our controller, and Play will create an instance of the repository and inject it into the constructor for us.We'll also take this opportunity to add the methods that allow us to retrieve blog posts and comments.
Open
ApiController.scala
and modify it to look like the following:// app/controllers/ApiController.scala // ... other imports // NEW - import JSON functionality and our data repository import play.api.libs.json.Json import repositories.DataRepository @Singleton class ApiController @Inject()(cc: ControllerComponents, dataRepository: DataRepository // NEW ) extends AbstractController(cc) { // Create a simple 'ping' endpoint for now, so that we // can get up and running with a basic implementation def ping = Action { implicit request => Ok("Hello, Scala!") } // NEW - Get a single post def getPost(postId: Int) = Action { implicit request => dataRepository.getPost(postId) map { post => // If the post was found, return a 200 with the post data as JSON Ok(Json.toJson(post)) } getOrElse NotFound // otherwise, return Not Found } // NEW - Get comments for a post def getComments(postId: Int) = Action { implicit request => // Simply return 200 OK with the comment data as JSON. Ok(Json.toJson(dataRepository.getComments(postId))) } }
Here we have created two new endpoints: one for retrieving a post by ID, and one for retrieving comments for a post given the post ID. To support that, we've brought in two new imports, and also added our
DataRepository
type to the class constructor.So that we can test out our new functionality, we need to add in a couple of routes that point to these two new methods. Open
conf/routes
and add these two new entries in addition to the /api/ping
route we had earlier:GET /api/ping controllers.ApiController.ping # NEW GET /api/post/:postId controllers.ApiController.getPost(postId: Int) GET /api/post/:postId/comments controllers.ApiController.getComments(postId: Int)
Finally, let's test the changes to see if we can access our blog data via an HTTP call. If you have stopped your application from running, you can restart it in the terminal by running:
sbt run 9000
From another terminal window, issue requests to our API using
curl
:# Request a blog post curl localhost:9000/api/post/1 # Request comments for a post curl localhost:9000/api/post/1/comments
You should find that the server will correctly return the post and comment data as JSON!
Adding Authentication
Now that we have a working API, let's set about securing it using access tokens. This will mean that only clients that have a valid access token for this API will be able to call our endpoints and retrieve data. In a real-world scenario, access tokens would be retrieved as part of an OAuth flow originating from some front-end application (or perhaps a CLI in the case of a machine-to-machine application). We don't have a front-end for this example, so we're going to be using Auth0 to issue our access tokens that are then validated and accepted or rejected by our API.
On a technical level, we're going to implement this by creating a custom action that processes requests as they come into our API. A kind of middleware, if you like. Inside this custom action, we'll extract the bearer token, decode it, and then validate it to make sure that it is indeed a valid token and that the caller can access our API. We can then apply this custom action to any endpoints where the caller needs to be authenticated in order for the call to succeed.
Using JSON Web Tokens
For our API, we expect the token to be a JSON Web Token (JWT), sent to us in the Authorization header. The JWT is made up of three parts: the header, the claims, and the signature. The signature is calculated using the header and the claims, so we can be sure that the data inside the token hasn't been tampered with. Furthermore, since the JWT has been signed using Auth0's private key, if we're able to verify the signature using their corresponding public key, then we know that the token has originated from the place we expect.
Given that the JWTs that we receive will be cryptographically signed using a public/private key algorithm, we need to have the public key available so that we can verify the signature. We can get the public key from the JSON Web Key Set (JWKS) endpoint. This is essentially a set of public keys that allow us to verify RS256 signatures on JWTs. The endpoint exists as part of your Auth0 domain and can be accessed through the browser. An example of the data that it returns is as follows (the keys have been shortened for brevity):
{ "keys": [ { "alg": "RS256", "kty": "RSA", "use": "sig", "x5c": [ "MIIDCzCCAfOgAwIBAgIJAd7LS...zo6379" ], "n": "3GlXBGJQlgJh...I6nGE6PzSvb5ZAw", "e": "AQAB", "kid": "NTgwMENENDFF...2RDJDMkEyNzYyRkY3MA", "x5t": "NTgwMENENDFF...2RDJDMkEyNzYyRkY3MA" } ] }
Adding dependencies
In our sample application, we're going to use
, an open-source library to download and retrieve the public key for us. We're also going to use jwks-rsa
, which will take this public key and decode the token for us, as well as perform some basic validation.jwt-scala
Let's add these dependencies now. Open your
build.sbt
file that is in the root of the project folder, and add the dependencies in underneath the dependencies that have already been registered:// ... other dependencies libraryDependencies ++= Seq( "com.pauldijou" %% "jwt-play" % "0.19.0", "com.pauldijou" %% "jwt-core" % "0.19.0", "com.auth0" % "jwks-rsa" % "0.6.1" )
Processing the token
Next, let's add a class we can use to process and validate a given JWT. Create a new folder inside
app
called auth
and a new file in there called AuthService.scala
:mkdir app/auth && touch app/auth/AuthService.scala
Open
AuthService.scala
. We'll start by adding a class with a method that will take a token and return the JWT claims:// app/auth/AuthService.scala package auth import com.auth0.jwk.UrlJwkProvider import javax.inject.Inject import pdi.jwt.{JwtAlgorithm, JwtBase64, JwtClaim, JwtJson} import play.api.Configuration import scala.util.{Failure, Success, Try} class AuthService @Inject()(config: Configuration) { // A regex that defines the JWT pattern and allows us to // extract the header, claims and signature private val jwtRegex = """(.+?)\.(.+?)\.(.+?)""".r // Your Auth0 domain, read from configuration private def domain = config.get[String]("auth0.domain") // Your Auth0 audience, read from configuration private def audience = config.get[String]("auth0.audience") // The issuer of the token. For Auth0, this is just your Auth0 // domain including the URI scheme and a trailing slash. private def issuer = s"https://$domain/" // Validates a JWT and potentially returns the claims if the token was // successfully parsed and validated def validateJwt(token: String): Try[JwtClaim] = for { jwk <- getJwk(token) // Get the secret key for this token claims <- JwtJson.decode(token, jwk.getPublicKey, Seq(JwtAlgorithm.RS256)) // Decode the token using the secret key _ <- validateClaims(claims) // validate the data stored inside the token } yield claims }
The core of this is the
validateJwt
method, which takes a string representing the token, and returns Try[JwtClaim]
, which will succeed if the token is able to be parsed and validated, or fail if there was a problem. JwtClaim
is a type from the jwt-core
dependency we added earlier. Despite what the type name suggests, this type holds all the claims that were retrieved from the token.In order to fully process and validate the token, this method relies on a few other private methods in order to do its job. Let's implement
getJwk
, which will extract the JSON web key from the JWKS data. These functions can go in the same class, underneath validateJwt
:// app/auth/AuthService.scala // .. leave untouched .. def validateJwt(token: String): Try[JwtClaim] = for { ... } // .. add the new methods below 'validateJwt' // Splits a JWT into it's 3 component parts private val splitToken = (jwt: String) => jwt match { case jwtRegex(header, body, sig) => Success((header, body, sig)) case _ => Failure(new Exception("Token does not match the correct pattern")) } // As the header and claims data are base64-encoded, this function // decodes those elements private val decodeElements = (data: Try[(String, String, String)]) => data map { case (header, body, sig) => (JwtBase64.decodeString(header), JwtBase64.decodeString(body), sig) } // Gets the JWK from the JWKS endpoint using the jwks-rsa library private val getJwk = (token: String) => (splitToken andThen decodeElements) (token) flatMap { case (header, _, _) => val jwtHeader = JwtJson.parseHeader(header) // extract the header val jwkProvider = new UrlJwkProvider(s"https://$domain") // Use jwkProvider to load the JWKS data and return the JWK jwtHeader.keyId.map { k => Try(jwkProvider.get(k)) } getOrElse Failure(new Exception("Unable to retrieve kid")) }
Here,
getJwk
composes a couple of utility functions that split the token into its 3 component parts (header, claims, and signature), decodes the header and claims from base64 strings, and then extracts the KID from the header of the token. This KID is then used to look up the correct JSON Web Key (JWK) from the JWKS data. If it is found, the JWK is returned.Finally, we need to implement
validateClaims
. In general, when validating a token you want to make sure that the current date is between the token's "not before" date and its expiry date (if the JWT contains such claims), as well checking that the token issuer and audience value are what you expect. Luckily for us, the JwtClaim.isValid
function takes care of these validation requirements for us.Let's put a thin wrapper around this function so that it supports returning
Try[JwtClaim]
. Again, this function can just go underneath the other methods that we've already put in:// app/auth/AuthService.scala // Validates the claims inside the token. 'isValid' checks the issuedAt, expiresAt, // issuer and audience fields. private val validateClaims = (claims: JwtClaim) => if (claims.isValid(issuer, audience)) { Success(claims) } else { Failure(new Exception("The JWT did not pass validation")) }
The last thing to do here is to modify our configuration file so that the values for
auth0.domain
and auth0.audience
exist and can be read. Open the conf/application.conf
file now and populate it with the following:auth0 { domain = ${?AUTH0_DOMAIN} audience = ${?AUTH0_AUDIENCE} }
With these settings, we can set
AUTH0_DOMAIN
and AUTH0_AUDIENCE
as environment variables that can subsequently be read by Play's configuration system.Creating our custom action
The next thing to do is create the custom action that will process our requests and validate the bearer tokens on them. Create a new file in the
app/auth
folder called AuthAction.scala
:touch app/auth/AuthAction.scala
Then populate it with the following code:
package auth import javax.inject.Inject import pdi.jwt._ import play.api.http.HeaderNames import play.api.mvc._ import scala.concurrent.{ExecutionContext, Future} import scala.util.{Failure, Success} // A custom request type to hold our JWT claims, we can pass these on to the // handling action case class UserRequest[A](jwt: JwtClaim, token: String, request: Request[A]) extends WrappedRequest[A](request) // Our custom action implementation class AuthAction @Inject()(bodyParser: BodyParsers.Default, authService: AuthService)(implicit ec: ExecutionContext) extends ActionBuilder[UserRequest, AnyContent] { override def parser: BodyParser[AnyContent] = bodyParser override protected def executionContext: ExecutionContext = ec // A regex for parsing the Authorization header value private val headerTokenRegex = """Bearer (.+?)""".r // Called when a request is invoked. We should validate the bearer token here // and allow the request to proceed if it is valid. override def invokeBlock[A](request: Request[A], block: UserRequest[A] => Future[Result]): Future[Result] = extractBearerToken(request) map { token => authService.validateJwt(token) match { case Success(claim) => block(UserRequest(claim, token, request)) // token was valid - proceed! case Failure(t) => Future.successful(Results.Unauthorized(t.getMessage)) // token was invalid - return 401 } } getOrElse Future.successful(Results.Unauthorized) // no token was sent - return 401 // Helper for extracting the token value private def extractBearerToken[A](request: Request[A]): Option[String] = request.headers.get(HeaderNames.AUTHORIZATION) collect { case headerTokenRegex(token) => token } }
The key part here is the
invokeBlock
method, which is called by Play as part of fulfilling a request to an endpoint. The original request is passed as an argument along with a function called block
that represents the next step in the request. If the request is valid, you would call block
in order to signal that the request should continue through the pipeline. Otherwise, a different result can be returned if it's decided that the request should not continue for whatever reason.Our implementation is fairly simple. We first extract the
Authorization
header from the request and retrieve the token from the header value. We then use our AuthService.valiateJwt
method to validate the token and extract the claims. If we successfully manage to get the claims, then the token has been validated and we should continue with the request. Otherwise, if there was a failure or some other reason why we could not retrieve the claims, then an Unauthorized
result is returned. Similarly, if no token was present in the request or the header was missing, then we likewise return Unauthorized
.Securing our endpoints
The last thing we need to do is apply this custom action to our endpoints that we want to secure. Open up our
ApiController
once again and make the following changes to the controller class:// app/controllers/ApiController.scala import auth.AuthAction // NEW - import our custom AuthAction // .. other imports @Singleton class ApiController @Inject()( cc: ControllerComponents, dataRepository: DataRepository, authAction: AuthAction // NEW - add the action as a constructor argument ) extends AbstractController(cc) { def ping = Action { implicit request => // .. unchanged code } // Get a single post // NEW - change the action type to 'authAction' def getPost(postId: Int) = authAction { implicit request => // .. unchanged code } // Get comments for a post // NEW - change the action type to 'authAction' def getComments(postId: Int) = authAction { implicit request => // .. unchanged code } }
To make use of our custom action, we first need to import it. Then we add the action into the constructor of the class so that it is injected by Play. Finally, on each endpoint we want to secure, we change the action type to our
authAction
instance instead of the default Action
. That is:// old def getPost(postId: Int) = Action { implicit request => // .. unchanged code } // new def getPost(postId: Int) = authAction { implicit request => // .. unchanged code }
Now whenever these endpoints are called, they will filter through our custom action and require that a valid access token is supplied in the request in order for the call to work.
In order for us to test this, we need to create an API application within Auth0 so that it can issue us some valid tokens. Let's do that now.
Creating the Auth0 API
Sign-in to your Auth0 account (or register for a free account if you don't already have one). Once you are on the dashboard, select "APIs" on the left to enter the API dashboard screen.
Next, click the red "Create API" button. Here we're asked to enter a name for our API and an identifier. For these, I've used "Scala API demo" and "https://scala-api.example.com" respectively. Leave the "Signing Algorithm" field as the default of "RS256".
Note: The value for
here will be used as your API audience when you come to run the application in the next step.identifier
Then hit the "Create" button. On the page that follows, click on the "Test" link. This is where we can retrieve an access token to test our API's authentication mechanism. To retrieve a valid token, click the 'copy token' link that is underneath the request example.
Next, start your application running by using the following command, which will set the proper environment variables. If your application is already running, you should stop it and run the following instead:
export AUTH0_DOMAIN=<your Auth0 domain> # e.g. your-tenant.auth0.com export AUTH0_AUDIENCE=<your Auth0 API identifier> # e.g. https://scala-api.example.com sbt run 9000
Note You should substitute in your own values for
andAUTH0_DOMAIN
.AUTH0_AUDIENCE
To test the API's authentication mechanism, let's first make an unauthenticated request to the server:
curl -i localhost:9000/api/post/1
Example response:
HTTP/1.1 401 Unauthorized Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin X-Frame-Options: DENY X-XSS-Protection: 1; mode=block X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'self' X-Permitted-Cross-Domain-Policies: master-only Date: Tue, 13 Nov 2018 14:25:06 GMT Content-Length: 0
We should have correctly received a 401 Unauthorized response from the app! Now, let's make sure the same call works with a valid access token. You'll need to copy and paste your token from Auth0 here where it says
<your access token>
:curl -i \ -H 'Authorization: Bearer <your access token>' \ localhost:9000/api/post/1 # Response HTTP/1.1 200 OK Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin X-Frame-Options: DENY X-XSS-Protection: 1; mode=block X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'self' X-Permitted-Cross-Domain-Policies: master-only Date: Tue, 13 Nov 2018 14:29:18 GMT Content-Type: application/json Content-Length: 40 {"id":1,"content":"This is a blog post"}%
This time we should have received a 200 OK response with the JSON data that we expect!
“I successfully built a Scala API and secured it using JWTs!”
Tweet This
Summary
In this tutorial, we've learned how to create a basic API using Scala and the Play Framework. We also saw the steps needed to parse and validate JSON Web Tokens as requests are made into our app. We've then gone on to set up an API within Auth0 and used it to issue access tokens that we can then validate using our API.
About the author
Steve Hobbs
Developer Experience Engineer