TUTORIAL

Securing CRUD API With JSON Web Token (JWT) On Express and Lyrid Serverless Function

Handoyo Sutanto
10 minutes
January 25, 2021
Handoyo Sutanto
10 minutes
January 25, 2021

In most cases, when building API for your services, you will need a way to verify if the user is properly authenticated to read the data that belongs to the user. The calls will need to be authenticated and the identity of the caller needs to be passed down to the service to make the appropriate database calls that only show relevant data to that user.

In this tutorial, we will continue our Lyrid Serverless Function builds from our previous NodeJS post to build an API endpoint that is protected, able to identify its caller user identity, and only update fields that belong to this user.

For this post, we are crediting these 2 blog posts as we are creating a remix of these 2 posts to show how to create a login, along with the protected routes that do CRUD (Create-Read-Update-Delete) API using standard NodeJS Express and to publish them into Lyrid Function:

1. Tutorial for creating NodeJS protected routes using JWT by Jason Watmore:

https://jasonwatmore.com/post/2018/08/06/nodejs-jwt-authentication-tutorial-with-example-api

2. Tutorial for NodeJS CRUD API using PostgesSQL by Geshan Manandhar:

https://geshan.com.np/blog/2021/01/nodejs-postgresql-tutorial/

These 2 blog posts are an excellent starter to grasp the concepts that are usually required to understand how we build API services that know the “user” and its scope on how to read/write/update his record in the backend database. The full code for this is hosted on this GitHub page:

https://github.com/LyridInc/lyrid-jwt-crud-expressjs.git

What is JWT and Why?

In an effort to make this post shorter, we will not dive too deep into how JWT works. In short, it describes the flow of authentication and authorization in HTTP calls, example are as follows:

https://developer.okta.com/blog/2019/02/14/modern-token-authentication-in-node-with-express

JWT is a 2 step authentication and authorization process that uses standard HTTP Request and Response to grant a user access into restricted API calls. The steps are as follows:

  1. Authentication:
    Usually requires the user to pass in username and password (in some systems it can be a verification token from email or SMS) into an authentication endpoint of the service.
    This authentication endpoint will check for the credentials and grant access by creating an authorization token in form of a signed JWT that is generated by a private key in form of a secret string or certificate. Anyone can read the JWT information but only systems that have the secret string or certificate can verify these tokens.
    Internally, the JWT can contain information regarding the scope of the API call (user id, email address, etc), along with token issue time and expiration time. A shorter expiration time is usually better for security, but it does introduce more calls into the authentication endpoint.
  2. Authorization:
    During any authenticated API call, the caller will need to pass authorization in the form of an Authorization Bearer token header. This contains the JWT that is generated on the first step.
    The API service will need to share the same secret or certificate that allows the API server to verify the token without the need to communicate with the authentication service. This way, the REST API will actually carry an identity without containing any information about the credential of the user.

By decoupling the Authentication and Authorization step, developers will then have the ability to also decouple their APIs with their user authentication and management that has major advantages like:

https://dzone.com/articles/jwtjson-web-tokens-are-better-than-session-cookies

No Session to Manage (stateless): The JWT is a self contained token which has authentication information, expire time information, and other user defined claims digitally signed.

Portable: A single token can be used with multiple backends.

No Cookies Required, So It’s Very Mobile Friendly

Good Performance: It reduces the network round trip time.

Decoupled/Decentralized: The token can be generated anywhere. Authentication can happen on the resource server, or easily separated into its own server.

JWT is one of the techniques that is commonly used in HTTP authorization and there are other tutorials there that explain JWT on how it is being used. Here are some additional information on the topic:

https://auth0.com/docs/security/tokens/json-web-tokens

Prerequisites

Setup

First, let's clone our repository and run them:

git clone https://github.com/LyridInc/lyrid-jwt-crud-expressjs.git
cd lyrid-jwt-crud-expressjs
npm install

The next step is that we need to inject seed data into this SQL for testing the application. You can do that by pasting the data inside db/init.sql into the SQL browser of ElephantSQL and click on execute:

For those who can’t read SQL queries, all this script does is create a new table and inserts data into the database.

If you read and follow Geshan’s post, you will notice that the original SQL query from the original post does not have user-id information, and this modified query has an ID of 1 or 2 in the database that represents the user ID.

The next step is to copy the connection string into the environment file. Open your .env file and put your PostgreSQL URL. You will also need to generate a string that will be used to sign the JWT, you can use a randomly generated string of 32–64 characters. The final environment file looks something like this:

Now you can run your REST API to test them by calling:

npm start

Now that it is running, we dive deeper into the additional middlewares and routes from our previous post. We added a few additional configurations in our src/entry/entry.js that does the following:

  • Configures JWT middleware to secures the API with rules.
  • Create login endpoint.
  • Create protected CRUD endpoints.

Let’s dive deeper into this middleware and endpoint.

Authentication Endpoint

Inspired by the blog post from Jason @ https://jasonwatmore.com/, we created the middleware that handles POST request with the path of /login as follows:

The login will take in the username and password as a body of the request, and create a JWT (using a library called jsonwebtoken) that has the user ID that expires in 7 days. This response is then signed by the secret string that is inside our environment.

Configuring Protected/Unprotected Routes

After coding the login endpoint, we need to configure our Express application to be able to handle JWT bearer tokens in the header by adding a middleware library called express-jwt.

This middleware allows every single request to be authenticated, and we are able to configure the endpoints that don’t need to be authenticated as follows:

The login endpoint will not be authenticated and for backward compatibility, we will include the original routes from our first blog post to be unprotected.

Protected Routes and CRUD API

Since these calls are protected by JWT middleware, all the internal APIs have information about the user context, and the internal SQL queries can be generated using that context.

Here are the resulting protected endpoints:

GET /quotes — Get a page that contains quotes for the following user. It employs pagination as a technique to keep response small and faster user experience.

POST /quotes — Inserts a quote for the following user with data from the request body.

PATCH /quotes/:quoteId — Updates an existing quote with id with data from the request body.

DELETE /quotes/:quoteId — Deletes a quote with id.

Internally, the update and delete verify the id and only does the operation if the user id is the same as the id in the database.

Digging deeper into one of the quotes will show the SQL commands that are generated. For example, opening the src/protected/service.js, and looking at the code flow, validating the input, and injects the parameters into a SQL query, and passed the query into the db.query() function:

Where the db.query() code is as follows:

It has a connection to the database and this is where the SQL queries are executed against the PostgreSQL URL that is configured early in the tutorial.

That’s it, we are ready to run and test this out.

Test and Deployment

Let’s test this login with the same username and password that is hardcoded into src/users/list.js,

{ "username": "user1",
"password": "password"}

Your response will be like this:

The JWT token to authorize the subsequent requests will be in the response as shown above.

Pro Tip: Anyone can read the content of your JWT using any tools out there (example: https://jwt.io/) as it is a standard. Only the servers that have the secret strings can verify if this token is actually valid, so do not share your secret key with any system that is not authorized to do verify the token.

Run GET /quotes for the next request, but you will need to copy and paste the token generated into the Bearer token Authorization:

As you can see, the API request will only return the quotes that belong to the user that is associated with the user.

To insert new quotes data for the following user, call the following endpoint with the same token:

POST /quotes —

{ "quote": "Where is the beef?",
"author": "Handoyo Sutanto"}

As you insert them, you can call GET /quotes again to see the quote that you inserted.

Now we tested the new endpoints, let's submit this function into Lyrid platform by calling:

lc code submit

Passing Authorization Header in Lyrid API Gateway

This part of the tutorial is specific to Lyrid and the Universal API Gateway implementation.

Now that your function is up there in our platform, how do you pass the Authorization Bearer token? As you recall from our previous posts, the Authentication header is already used by our platform to identify the execution against the Lyrid account.

For this purpose, we developed an additional header that our users can use to pass the Authorization into their serverless function. X-Lyrid-Authorization is a Lyrid specific header that will be handled by the platform. The platform will pass down the value of this header as an Authorization header.

Set the X-Lyrid-Authorization Header as following (remember to include the Bearer string in there):

We are currently working on creating a unique execution endpoint that works on a top subdomain of Lyrid that doesn’t require you to pass Basic Authentication header.

Conclusion

Obviously, we won’t use a hardcoded username and password in production. Since the authentication service is decoupled, there are options to do a managed authentication services (example: Okta, Auth0, or AWS Cognito). These services can help developers to authenticate their username and password in a separate secured environment and generates JWT without the need to host in their own environment.

This allows developers to offload the user and password management and focus more on the API that they are building. As shown in this tutorial, we support the usage of any external authentication services into your Lyrid Serverless Function using this method.

As for Lyrid, we utilize JWT heavily inside our connected services that drive all the function build, execution, and deployment. We have developed a modification of this open-source GraphQL Authentication:

https://django-graphql-auth.readthedocs.io/en/latest/

This enables us to do our own self-hosted authentication management that does the following:

  • Python 3.8, Django 3.0.5 application on Lyrid Serverless platform.
  • Uses GraphQL endpoint and mutations for authentication API.
  • Connects to almost any database, e.g.: PostgreSQL, MySQL, or even MongoDB.
  • Manages user ID, logins, and tokens.
  • Connects user ID to other Single-Sign-On services to enable login with external login services (GitHub, Facebook, Google).
  • 2-Factor-Authentication plugin.
  • Configuring communication settings (Email or SMS).
  • Administrator dashboard to be able to reset any user’s password.

All of them, packed into the Lyrid Serverless application. We will show you how to instantiate, run, and use this service easily inside your Lyrid Account. This is for our future blogs, but please don’t be shy to contact me hsutanto@lyrid.io or join our Slack channel to say hi if you want to learn more about this!

Thank you for reading!🐱‍🏍Stay tuned in for more tutorials!

Handoyo

Schedule a demo

Let's discuss your project

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

99 South Almaden Blvd. Suite 600
San Jose, CA
95113

Jl. Pluit Indah 168B-G, Pluit Penjaringan,
Jakarta Utara, DKI Jakarta
14450

copilot