Overview
LoopBack 4 has an authentication package @loopback/authentication
which allows
you to secure your application’s API endpoints with custom authentication
strategies and an @authenticate
decorator.
This tutorial shows you how to add JWT authentication to your LoopBack
application using the extension
@loopback/authentication-jwt
.
For demonstration purpose, we will be using the Todo application as the base application.
At the end of this tutorial, it supports the following scenarios:
- User needs to log in first to get the token before calling any of the todo endpoints
- User can sign up if they don’t have any account already
- If the token is valid, user can call the REST APIs, otherwise an error with 401 status code will occur.
JSON Web Token Approach
Here is a brief summary of the JSON Web Token (JWT)
approach.
In the JSON Web Token (JWT) authentication approach, when the user provides the correct credentials to a login endpoint, the server creates a JWT token and returns it in the response. The token is of type string and consists of 3 parts: the header, the payload, and the signature. The header and payload are digitally signed with secret, and the parts are separated by a period.
For example:
// {base64UrlEncode-header}.{base64UrlEncode-payload}.{encrypted-signature}
eyJhbXVCJ9.eyJpZCI6Ij.I3wpRNCH4;
// actual parts have been reduced in size for viewing purposes
Note: The payload can contain anything the application developer wants, but at the very least contains the user id. It should never contain the user password.
After logging in and obtaining this token, whenever the user attempts to access a protected endpoint, the token must be provided in the Authorization header. The server verifies that the token is valid and not expired, and then permits access to the protected endpoint.
Please see JSON Web Token (JWT) for more details.
To understand the details of how JWT authentication can be added to a LoopBack 4 application, read the Adding JWT Authentication to a LoopBack 4 Application section.
Before we begin
Let’s download the Todo example and install the @loopback/authentication-jwt
extension.
$ lb4 example todo
$ cd loopback4-example-todo
$ npm i --save @loopback/authentication @loopback/authentication-jwt
Step 1: Bind JWT Component in the Application
In src/application.ts
, bind the authentication components to your application
class.
// ---------- ADD IMPORTS -------------
import {AuthenticationComponent} from '@loopback/authentication';
import {
JWTAuthenticationComponent,
SECURITY_SCHEME_SPEC,
UserServiceBindings,
} from '@loopback/authentication-jwt';
import {DbDataSource} from './datasources';
// ------------------------------------
export class TodoListApplication extends BootMixin(
ServiceMixin(RepositoryMixin(RestApplication)),
) {
constructor(options: ApplicationConfig = {}) {
//...
// ------ ADD SNIPPET AT THE BOTTOM ---------
// Mount authentication system
this.component(AuthenticationComponent);
// Mount jwt component
this.component(JWTAuthenticationComponent);
// Bind datasource
this.dataSource(DbDataSource, UserServiceBindings.DATASOURCE_NAME);
// ------------- END OF SNIPPET -------------
}
}
Step 2: Add Authenticate Action
Note: Skip this step when using a middleware-based sequence, which is used by default on newly-generated LoopBack 4 applications.
Next, we will add the authenticate action in the Sequence. We’ll also modify the error when authentication fails to return status code 401.
// ---------- ADD IMPORTS -------------
import {
AuthenticateFn,
AuthenticationBindings,
AUTHENTICATION_STRATEGY_NOT_FOUND,
USER_PROFILE_NOT_FOUND,
} from '@loopback/authentication';
// ------------------------------------
export class MySequence implements SequenceHandler {
constructor(
// ---- ADD THIS LINE ------
@inject(AuthenticationBindings.AUTH_ACTION)
protected authenticateRequest: AuthenticateFn,
) {}
async handle(context: RequestContext) {
try {
const {request, response} = context;
const route = this.findRoute(request);
// - enable jwt auth -
// call authentication action
// ---------- ADD THIS LINE -------------
await this.authenticateRequest(request);
const args = await this.parseParams(request, route);
const result = await this.invoke(route, args);
this.send(response, result);
} catch (err) {
// ---------- ADD THIS SNIPPET -------------
// if error is coming from the JWT authentication extension
// make the statusCode 401
if (
err.code === AUTHENTICATION_STRATEGY_NOT_FOUND ||
err.code === USER_PROFILE_NOT_FOUND
) {
Object.assign(err, {statusCode: 401 /* Unauthorized */});
}
// ---------- END OF SNIPPET -------------
this.reject(context, err);
}
}
}
Step 3: Create the UserController for login
In the UserController, we are going to create three endpoints:
/login
for users to provide credential to login/whoami
for showing who is the current user/signup
for users to sign up
3a. Create the UserController
Create the controller using lb4 controller
command with the empty controller
option.
$ lb4 controller
? Controller class name: User
Controller User will be created in src/controllers/user.controller.ts
? What kind of controller would you like to generate? Empty Controller
create src/controllers/user.controller.ts
update src/controllers/index.ts
Controller User was created in src/controllers/
3b. Add endpoints in UserController
For UserService which verifies the credentials, we are going to use the default
implementation in the @loopback/authentication-jwt
extension. Therefore, the
constructor injects the MyUserService
from this extension.
// ---------- ADD IMPORTS -------------
import {inject} from '@loopback/core';
import {
TokenServiceBindings,
MyUserService,
UserServiceBindings,
UserRepository,
} from '@loopback/authentication-jwt';
import {TokenService} from '@loopback/authentication';
import {SecurityBindings, UserProfile} from '@loopback/security';
import {repository} from '@loopback/repository';
// ----------------------------------
constructor(
@inject(TokenServiceBindings.TOKEN_SERVICE)
public jwtService: TokenService,
@inject(UserServiceBindings.USER_SERVICE)
public userService: MyUserService,
@inject(SecurityBindings.USER, {optional: true})
public user: UserProfile,
@repository(UserRepository) protected userRepository: UserRepository,
) {}
For the implementation of all the 3 endpoints, you can take a look at this
user.controller.ts
in the [todo-jwt
example].
Step 4: Protect the Todo APIs
Finally, we need to protect other endpoints that we have, i.e. the /todos
APIs. Go to src/controllers/todo.controller.ts
. Simple add
@authenticate('jwt')
before the TodoController
class. This will protect all
the APIs in this controller.
// ---------- ADD IMPORTS -------------
import {authenticate} from '@loopback/authentication';
// ------------------------------------
@authenticate('jwt') // <---- Apply the @authenticate decorator at the class level
export class TodoController {
//...
}
If there are particular API that you want to make it available to everyone
without authentication, you can add @authenticate.skip()
before that function.
See https://loopback.io/doc/en/lb4/Decorators_authenticate.html for more
details.
Try it out
Start the application by running npm start and go to http://localhost:3000/explorer.
Note:
When using the API Explorer, be sure to clear out any default filter or where objects in order to see all the data.
You’ll see the 3 new endpoints under UserController
together with the other
endpoints under TodoController
.
-
Sign up using the
/signup
APISince we don’t have any users created, click on
POST /signup
. For the requestBody, the minimum you need isemail
andpassword
. i.e.{ "email": "testuser2@abc.com", "password": "testuser2" }
-
Log in using the
POST /users/login
APIAfter calling
/users/login
, the response body will look something like:{ "token": "aaaaaaaaa.aaaaaaaaaaaaaaaaa" }
Copy the token. Go to the top of the API Explorer, click the “Authorize” button.
Paste the token that you previously copied to the “Value” field and then click Authorize.
In the future API calls, this token will be added to the
Authorization
header . -
Get all todos using
GET /todos
API You should be able to call this API successfully.
Conclusion
Congratulations! You have successfully added the JWT authentication to the LoopBack application! We did the following:
- bind the JWT Component in the Application
- add Authenticate Action in the sequence
- create the UserController for login and signup functions
- protect the APIs by adding the
@authenticate
decorator
See the
todo-jwt example
for the working application. You can also run lb4 example todo-jwt
to download
the example.