Beautiful API using Scala based Playframework
We are going to build a beautiful REST API based on a modern playframework using Scala in a functional way.
Let us start by understanding the concept of REST in a simpler sense.
RESTful API
REST came from Roy Fieldings desertation in claiming to make web stateles again based on designing your system using nouns. So some of the examples are
- accounts
- users
- profiles
- logs
- assemblies
- apps
You no longer need the clugy SOAP heavyweight XML, but rather a nimble JSON using fundamental HTTP will do.
How do you arrive on a design for the RESTful approach for your system is beyond the scope of this article. However we recommend you to read about apigees REST API Design, which talks about the same.
The RESTful API’s transport mechanism is pure HTTP
We will use HMAC to encrypt the payload sent across from a client.
The client code we will use in our usecase will be Ruby or Scala.
First let us start with the core HTTP payload we would like to send across.
The constructs of the payload which would help us multi tenantized system are,
Parts of the payload.
- HEADER
Our header that we wish to send will have the following
X_Megam_EMAIL - The email address of the user X_Megam_APIKEY - The api key generated for the user X_Megam_DATE - The date on which the data was sent X_Megam_HMAC - The MD5 hmac calculated by the client in the format of email:hmac value Path - URL path from the HTTP Request
The HMAC value is calculated as follows X_Megam_DATE + Path + MD5(body JSON)
The HMAC string makes sure the payload sent from the client is well formed and is valid.
- BODY A JSON as sent by the client. The body JSON may or may not be present. In case where a HTTP GET is done there body will be empty
HEADER
Sample scala code in building Header.
val defaultHeaderOpt = Map(Content_Type -> application_json,
X_Megam_EMAIL -> "megam@mypaas.io", X_Megam_APIKEY -> "IamAtlas{74}NobodyCanSeeME#07",
//X_Megam_EMAIL -> "fake@mypaas.io", X_Megam_APIKEY -> "fakemypaas#megam", // X_Megam_EMAIL -> "steve@olympics.com", X_Megam_APIKEY -> "mAFfm45XnvYJMqPTpP5Dow==",
X_Megam_DATE -> currentDate, Accept -> application_vnd_megam_json)
BODY
Sample scala code.
val contentToEncode = "{\"collapsedmail\":\"megam@mypaas.io\", \"inval_api_key\":\"IamAtlas{74}NobodyCanSeeME#075488\", \"authority\":\"user\" }"
HMAC
We design a cryptographic hash based on the popular MD5 or SHA1 which can be encrypted during the send and decrypted upon receipt.
Now that we have set the playground for the various definitions let us put to use in the platframework.
Our RESTful API Serveer will be named as megam_gateway and is designed to be stateless.
Approach
We will use a novel approach to authenticate and authorize any request that come to our megam_gateway. The approach should be intelligent enought to authenticate any number of layers, return back on malformed header (invalid email could be one of them), mismatch API key and exception condition in megam_gateway.
Controller
A regular controller which the requests coming in to the megam_gateway.
play-auth
An authentication library that helps to quickly build authentication function in a play project.
APIAuthElement : Extension of StackActionController
A trait, that extends a StackActionController and can be invoked by extending this trait in relevant controller.
object Accounts extends Controller with APIAuthElement {
Also you have coarse control to decide if you wish to authenticate all HTTP verbs or not.
For instance when an account is created initially there is no point authenticating, you know why.
If an authentication is needed, then we wrap the particular action in controller using StackAction.
def show(id: String) = StackAction(parse.tolerantText) { implicit request =>
You can read the guide availabe in play-auth for more information.
SecurityActions
A helper function that handles authentication after everything is verified.
Lets put everything together and create our first api accounts
Step 1: FunnelRequest
The first step we do is to find those actions in the controller that require authentication. We wrap them with StackAction.
trait APIAuthElement extends StackableController
The trait has an implicit which helps to converts the input HTTPRequest inputs to a FunnelRequest The FunnelRequest just maps the HTTPRequestInput and maps the header, body, hmac to an internal object.
Step 2: Validate FunnelRequest
After that the SecurityActions.Authenticates the FunnelRequest Object and returns to serve the request if every is valid. If the Header is malformed its immediately sent back.
object SecurityActions {
def AuthenticatedA: ValidationNel[Throwable, Option[String]] = { Logger.debug((“%-20s –>[%s]”).format(“SecurityActions”, “Authenticated:Entry”)) req.funneled match {
Step3: Serve the request.
Now when things are validated, the controller handles the request
def show(id: String) = StackAction(parse.tolerantText) { implicit request =>
play.api.Logger.debug(("%-20s -->[%s]").format("controllers.Accounts", "show:Entry"))
play.api.Logger.debug(("%-20s -->[%s]").format("email", id))
models.Accounts.findByEmail(id) match {
We saw bits and pieces on how everything comes together. Let us look at how to implement an account..
Account
/accounts Let us implement the simple API which has the following requirements
HTTP Verb | REST API | Description |
GET | accounts | GET the account information of the user </tr> |
POST | accounts | Posts a new account |