cft

Implementing basic JWT-based authentication with LoopBack 4 and Docker for complete noobs (2/2)

Before we get started… this introduction to JWT authentication from the tutorial is very clear and helpful to understand the basics of the JWT authentication process.


user

Julia Mihet

2 years ago | 4 min read

In Part 1 of this tutorial, we started a new LoopBack 4 project, then set up a new MongoDB instance running in a Docker container and created a custom database and a root user to log in to our database. Now it’s time to build the API endpoints for user sign up and login.

This article will follow closely the official LoopBack 4 tutorial about securing an app with JWT authentication: https://loopback.io/doc/en/lb4/Authentication-tutorial.html#json-web-token-approach

Before we get started… this introduction to JWT authentication from the tutorial is very clear and helpful to understand the basics of the JWT authentication process:

A brief reminder of our implementation steps (from the previous article)

  1. Boot a MongoDB instance on localhost through Docker, with a database named test_db. DONE
  2. Create a new datasource in our LoopBack project, and connect it to the newly created test_db database. TO DO
  3. Implement the JWT authentication example in the LoopBack 4 tutorial. TO DO
  4. Test that the api endpoints we created work as expected. TO DO

Before we continue our implementation, we have to install some extra dependencies:

  • mongo LoopBack 4 connector
npm i --save loopback-connector-mongodb dotenc
  • LoopBack authentication packages
npm i --save @loopback/authentication @loopback/authentication-jwt

Dotenv setup

Remember how we wrote a .sh script in Part 1 to feed MongoDB with the credentials safely kept in our .env file? You thought we are done with that, but there is still an eetsy-beetsy part we didn’t (yet) deal with: npm also needs to know about the credentials (because our mongo.datasource.ts will also rely on those credentials to connect to MongoDB).

Here’s where dotenv comes to play.

Dotenv is a zero-dependency module that loads environment variables from a .env file into process.env (description copied from their npm page)

Quite handy, dont’ you think? Now, in order to actually use dotenv, we will change the npm start script in package.json as follows:

"start": "node -r dotenv/config -r source-map-support/register .",

-r argument means require (module to preload). So we want to preload dotenv/config when we run npm start .

Step 2: Add a new MongoDB data source to our app

Create a new data source through the LoopBack 4 CLI:

lb4 datasource

Make the following choices in the interactive dialogue:

Open mongo.datasource.ts and update the data source config:

const config = {
'name': 'mongo',
'connector': 'mongodb',
'url': `mongodb://${process.env.MONGO_INITDB_ROOT_USERNAME}:${process.env.MONGO_INITDB_ROOT_PASSWORD}@localhost:27017/test_db`,
'useNewUrlParser': true,
'useUnifiedTopology': true
};

We use the url method to log into our newly created test_db. Once logged in, we can now do operations on the database, like add users and query the existing users.

Step 3: Implement the JWT authentication example in the LoopBack 4 tutorial

Application is the main class of our LoopBack 4 app (found in the root of our project, inapplication.ts). This is where we bring together our project’s controllers, components, services and bindings so that we make them available to our entire app.

In this example, we will deal with components, which are nothing more than npm packages (you can read more about them here). Any developer can actually write a component for LoopBack 4 and then use it in a project.

In our case, we have to import components from the 2 packages we just installed, @loopback/authentication and @loopback/jwt-authentication. We will also import UserServiceBindings , which are predefined options to be used to create a new data source from our MongoDB datasource.

If we inspect the UserServiceBindings type, we can see:

export declare namespace UserServiceBindings {
const USER_SERVICE: BindingKey<UserService<User, Credentials>>;
const DATASOURCE_NAME = "jwtdb";
const USER_REPOSITORY = "repositories.UserRepository";
const USER_CREDENTIALS_REPOSITORY = "repositories.UserCredentialsRepository";
}

So, we will add the following code to application.ts

import {AuthenticationComponent} from '@loopback/authentication';
import {
JWTAuthenticationComponent,
UserServiceBindings
} from '@loopback/authentication-jwt';
import {MongoDataSource} from './datasources';

// in the class constructor, we mount the two components we just imported,
// AuthenticationComponent and JWTAuthenticationComponent
// We also create a new datasource from the MongoDataSource we created in the previous step
// and the UserServiceBindings, which define default options for the new datasource,
// like its name, the repository to use for Users and UserCredentials
// (which are in our case the default LoopBack 4 ones)

export class AuthExampleApplication extends BootMixin(
ServiceMixin(RepositoryMixin(RestApplication)),
) {
constructor(options: ApplicationConfig = {}) {
....

this.component(AuthenticationComponent);
// Mount jwt component
this.component(JWTAuthenticationComponent);
// Bind datasource
this.dataSource(MongoDataSource, UserServiceBindings.DATASOURCE_NAME);

....
}

Create the User Controller

For this part, I simply copied the code from the GitHub LoopBack 4 JWT example: https://github.com/strongloop/loopback-next/blob/master/examples/todo-jwt/src/controllers/user.controller.ts

The controller comes with 3 api endpoints:

  • /signup (for new users)
  • /users/login (for returning users)
  • /whoAmI (to see current user permissions)

Once this is done, we are left with one final implementation step… testing our API endpoints.

Step 4: Test that the api endpoints we created work as expected.

Boot the app:

npm start

Go to http://[::1]:3000/explorer. Here we can see all the api urls available in our project:

So now, we want to test the UserController api endpoints as follows:

  1. The “happy” flow: we create a new user via the /signup api endpoint. We then login with the new user’s credentials and we get back a JWT token.
  2. The “sad” flow: we try to login with an non-existent user (we should get back an unauthorized error).

Testing the “happy” flow

  • We expand the /singup panel, and we click on “Try it out”. We then fill in a mock user and password to be saved in our test_db database:

We press on “Execute”. We get a 200 code back (hopefully!). Our user was created.

Now to test the /users/login endpoint, we just take the same credentials we used above and call the api:

When we press on “Execute”, we can see we got code 200, but also we were returned a token:

In a real-world app, every time a logged in user makes requests to other api endpoints (like for example /users/{id}/profile, we will use this token in the request payload so that the server knows this user is authenticated.

Testing the “sad” flow

Now, we will simply try to login with a user that doesn’t exist:

As expected, we get back a `401 Unauthorized` response:

So, this means our API works as expected.

Yoohoo! We did it! We implemented a very basic signup/login API in LoopBack with a MongoDB instance running in a Docker container.

Here’s the GitHub repository for the code presented in these 2 articles: https://github.com/juliageek/lb4-docker-mongo-auth-example

Now go out there and build cool stuff (I know I will continue my pet project).

Upvote


user
Created by

Julia Mihet


people
Post

Upvote

Downvote

Comment

Bookmark

Share


Related Articles