DEV Community

Cover image for Securing and caching your Hyperlambda endpoints
Thomas Hansen
Thomas Hansen

Posted on

Securing and caching your Hyperlambda endpoints

In this article we build upon the work we did in our previous article where we created our first manual Hyperlambda endpoint wrapping an SQL query. This article also has an associated YouTube video you can find below.

Security and JWT tokens in Magic

Magic is built upon JWT. JWT is an Open Standard for authentication and authorisation. You can read more about JWT at JWT.io. JWT tokens are what's referred to as "transparent authorisation tokens". This implies you can parse them semantically in your frontend, to see things such as username, roles, and other claims associated with the user. In a later article we will look at how you can create JWT tokens in Magic to authenticate users, but for now realising how to secure your Hyperlambda endpoints such that only authorised users can invoke them is sufficient.

To demand that a user belongs to one or more roles, you'd typically use the [auth.ticket.verify] slot. This slot simply throws an exception unless your user belongs to one of the comma separated roles you pass in as an argument to its invocation. Below is the code we started out with in the above video for your convenience.

.arguments
   genre:string

// This line throws unless user belongs to root or admin role
auth.ticket.verify:root, admin

sqlite.connect:chinook

   sqlite.select:@"select distinct c.Email, c.FirstName, c.LastName, g.name
  from Customer c
    inner join Invoice i on c.CustomerId = i.CustomerId
    inner join InvoiceLine ii on i.InvoiceId = ii.InvoiceId
    inner join Track t ON ii.TrackId = t.TrackId
    inner join Genre g ON t.GenreId = g.GenreId
  where g.Name = @genre
  order by c.Email"
      @genre:x:@.arguments/*/genre

   return:x:-/*
Enter fullscreen mode Exit fullscreen mode

If you modify your Hyperlambda file from our previous article and exchange its content with the above, and you try to invoke the endpoint in an incognito browser window, you'll be given a 401 error saying "access denied". This is because your browser does not associate the correct JWT token with your request.

This allows you to apply RBAC for your endpoints, implying "Role Based Access Control", to control who's got access to invoke your endpoints. RBAC is a well known authorisation mechanism for controlling who's got access to your application, and of course managing users and roles in Magic is extremely easy. Below is a screenshot of adding a user to a role from your Magic dashboard.

RBAC administration in Magic

A user can belong to one or more roles, and each endpoint can be locked for all users except those belonging to one or more roles. This pattern allows you to easily control who's got access to invoke what endpoint on your server.

Caching and the HTTP response object

In the last parts of the above video we go through how you can add HTTP headers that are returned back to the client. Specifically, we apply the Cache-Control HTTP header, which allows the client to cache the response object for "n amount of seconds". For endpoints that rarely changes their result given the same QUERY parameters, this is an easy win in regards to optimisations, and makes sure you application becomes much faster, and that the frontend becomes much more responsive, since it allows the browser to cache the endpoint's result for some pre-defined amount of time. You can find the complete code we're using to apply cache below.

.arguments
   genre:string

auth.ticket.verify:root, admin

// This line of code will cache your response for 200 seconds
response.headers.set
   Cache-Control:private, max-age=200

sqlite.connect:chinook

   sqlite.select:@"select distinct c.Email, c.FirstName, c.LastName, g.name
  from Customer c
    inner join Invoice i on c.CustomerId = i.CustomerId
    inner join InvoiceLine ii on i.InvoiceId = ii.InvoiceId
    inner join Track t ON ii.TrackId = t.TrackId
    inner join Genre g ON t.GenreId = g.GenreId
  where g.Name = @genre
  order by c.Email"
      @genre:x:@.arguments/*/genre

   return:x:-/*
Enter fullscreen mode Exit fullscreen mode

Validators

A validator is kind of like a "reusable business rule component", and in Magic there exists server side validators for most things you can imagine, such as email addresses, integer numbers, date and time objects, etc - And they are typically one liners and easily applied in your Hyperlambda. Below is the example code from our YouTube video where we apply a validator for our [foo] integer argument.

.arguments
   genre:string
   foo:int

auth.ticket.verify:root, admin

// This makes the genre argument mandatory.
validators.mandatory:x:@.arguments/*/genre

// This ensure the [foo] argument is between 100 and 200
validators.integer:x:@.arguments/*/foo
   min:100
   max:200

response.headers.set
   Cache-Control:private, max-age=200

sqlite.connect:chinook

   sqlite.select:@"select distinct c.Email, c.FirstName, c.LastName, g.name
  from Customer c
    inner join Invoice i on c.CustomerId = i.CustomerId
    inner join InvoiceLine ii on i.InvoiceId = ii.InvoiceId
    inner join Track t ON ii.TrackId = t.TrackId
    inner join Genre g ON t.GenreId = g.GenreId
  where g.Name = @genre
  order by c.Email"
      @genre:x:@.arguments/*/genre

   return:x:-/*
Enter fullscreen mode Exit fullscreen mode

Notice the subtle parts above where [genre] is mandatory, but [foo] is not mandatory. However, if [foo] is given, it has to have a value between 100 and 200. If you wanted the [foo] argument to also be mandatory, you'd have to add a mandatory validator for it.

In later articles we will dive deeper into Hyperlambda validators.

Discussion (3)

Collapse
mshafiey profile image
Mohsen

Can I ask you why you choose JWT ?

Collapse
polterguy profile image
Thomas Hansen Author

Great question, and there are many good reasons for choosing JWT.

  • It's an open standard
  • It's a transparent token (implying the frontend can inspect it)
  • It's highly secure due to its internals of HMAC signature

Etc, etc, etc - It's not "perfect", but when I researched the issue, it was definitely the best auth mechanism at the time ... ^_^

Collapse
mshafiey profile image
Mohsen

🙏 thanks