Securing and caching your Hyperlambda endpoints
Tags: webdev, tutorial, security
Published: 2022-06-22 13:20:34

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](https://dev.to/polterguy/manually-creating-your-first-hyperlambda-endpoint-4c1j). This article also has an associated YouTube video you can find below.

{% embed https://www.youtube.com/watch?v=rAjAlSOHEhM %}

## 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](https://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:-/*
```

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](https://en.wikipedia.org/wiki/Role-based_access_control) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/53v4q8nxzlu3szz2d105.png)

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:-/*
```

## 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:-/*
```

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.