diff --git a/docs/site/imgs/auth-tutorial-apiexplorer.png b/docs/site/imgs/auth-tutorial-apiexplorer.png
new file mode 100644
index 000000000000..de960eae75c1
Binary files /dev/null and b/docs/site/imgs/auth-tutorial-apiexplorer.png differ
diff --git a/docs/site/imgs/auth-tutorial-auth-button.png b/docs/site/imgs/auth-tutorial-auth-button.png
new file mode 100644
index 000000000000..a68210f7528a
Binary files /dev/null and b/docs/site/imgs/auth-tutorial-auth-button.png differ
diff --git a/docs/site/imgs/auth-tutorial-jwt-token.png b/docs/site/imgs/auth-tutorial-jwt-token.png
new file mode 100644
index 000000000000..1a3d2dfe1757
Binary files /dev/null and b/docs/site/imgs/auth-tutorial-jwt-token.png differ
diff --git a/docs/site/tutorials/authentication/Authentication-Tutorial.md b/docs/site/tutorials/authentication/Authentication-Tutorial.md
index 12c70a66992f..b35dce5b44a3 100644
--- a/docs/site/tutorials/authentication/Authentication-Tutorial.md
+++ b/docs/site/tutorials/authentication/Authentication-Tutorial.md
@@ -13,10 +13,23 @@ 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 showcases how `authentication` was added to the
-[loopback4-example-shopping](https://github.com/strongloop/loopback4-example-shopping)
-application by **creating** and **registering** a custom authentication strategy
-based on the `JSON Web Token (JWT)` approach.
+This tutorial shows you how to add JWT authentication to your LoopBack
+application using the extension
+[`@loopback/authentication-jwt`](https://github.com/strongloop/loopback-next/tree/master/extensions/authentication-jwt).
+
+For demonstration purpose, we will be using the
+[Todo application](https://loopback.io/doc/en/lb4/todo-tutorial.html) 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.
@@ -48,1298 +61,251 @@ permits access to the protected endpoint.
Please see [JSON Web Token (JWT)](https://en.wikipedia.org/wiki/JSON_Web_Token)
for more details.
-To view and run the completed `loopback4-example-shopping` application, follow
-the instructions in the [Try it out](#try-it-out) section.
-
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](#adding-jwt-authentication-to-a-loopback-4-application)
section.
-## Try it out
-
-If you'd like to see the final results of this tutorial as an example
-application, follow these steps:
-
-1. Start the application:
-
- ```sh
- git clone https://github.com/strongloop/loopback4-example-shopping.git
- cd loopback4-example-shopping
- npm install
- npm run docker:start
- npm start
- ```
-
- Wait until you see:
-
- ```sh
- Recommendation server is running at http://127.0.0.1:3001.
- Server is running at http://[::1]:3000
- Try http://[::1]:3000/ping
- ```
-
-1. In a browser, navigate to [http://[::1]:3000](http://127.0.0.1:3000) or
- [http://127.0.0.1:3000](http://127.0.0.1:3000), and click on `/explorer` to
- open the `API Explorer`.
-
-1. In the `UserController` section, click on `POST /users`, click on
- `'Try it out'`, specify:
-
- ```json
- {
- "email": "user1@example.com",
- "password": "thel0ngp@55w0rd",
- "firstName": "User",
- "lastName": "One"
- }
- ```
-
- and click on `'Execute'` to **add** a new user named `'User One'`.
-
-1. In the `UserController` section, click on `POST /users/login`, click on
- `'Try it out'`, specify:
-
- ```json
- {
- "email": "user1@example.com",
- "password": "thel0ngp@55w0rd"
- }
- ```
-
- and click on `'Execute'` to **log in** as `'User One'`.
-
- A JWT token is sent back in the response.
-
- For example:
-
- ```json
- {
- "token": "some.token.value"
- }
- ```
-
-1. Scroll to the top of the API Explorer, and you should see an `Authorize`
- button. This is the place where you can set the JWT token.
-
- 
-
-1. Click on the `Authorize` button, and a dialog opens up.
-
- 
-
-1. In the `bearerAuth` value field, enter the token string you obtained earlier,
- and press the `Authorize` button. This JWT token is now available for the
- `/users/me` endpoint we will interact with next. Press the `Close` button to
- dismiss the dialog.
-
- 
-
- {% include note.html content="The Logout button allows you to enter a new value; if needed." %}
-
-1. Scroll down to the `UserController` section to find `GET /users/me`
-
- 
+## Before we begin
- Notice it has a **lock** icon and the other endpoints in this section do not.
- This is because this endpoint specified an operation-level
- `security requirement object` in the OpenAPI specification. (For details, see
- the
- [Specifying the Security Settings in the OpenAPI Specification](#specifying-the-security-settings-in-the-openapi-specification)
- section.)
-
-1. Expand the `GET /users/me` section, and click on `Try it out`. There is no
- data to specify, so simply click on `Execute`. The JWT token you specified
- earlier was automatically placed in the `Authorization` header of the
- request.
-
- If authentication succeeds, the
- [user profile](https://github.com/strongloop/loopback-next/blob/master/packages/security/src/types.ts)
- of the currently authenticated user will be returned in the response. If
- authentication fails due to a missing/invalid/expired token, an
- [HTTP 401 UnAuthorized](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401)
- is thrown.
-
- The response contains a unique value in the `id` field (generated by the
- database) and `name` field with the full user name:
-
- ```json
- {"id": "5dd6acee242760334f6aef65", "name": "User One"}
- ```
-
-## Adding JWT Authentication to a LoopBack 4 Application
-
-In this section, we will demonstrate how `authentication` was added to the
-[loopback4-example-shopping](https://github.com/strongloop/loopback4-example-shopping)
-application using the
-[JSON Web Token (JWT)](https://en.wikipedia.org/wiki/JSON_Web_Token) approach.
-
-### Installing @loopback/authentication
-
-The `loopback4-example-shopping` application **already** has the
-`@loopback/authentication` dependency set up in its **package.json**
-
-It was installed as a project dependency by performing:
+Let’s download the Todo example and install the `@loopback/authentication-jwt`
+extension.
```sh
-npm install --save @loopback/authentication
+$ lb4 example todo
+$ cd loopback4-example-todo
+$ npm i --save @loopback/authentication @loopback/authentication-jwt
```
-### Adding the AuthenticationComponent to the Application
+## Step 1: Bind JWT Component in the Application
-The core of authentication framework is found in the
-[AuthenticationComponent](https://github.com/strongloop/loopback-next/blob/master/packages/authentication/src/authentication.component.ts),
-so it is important to add the component in the `ShoppingApplication` class in
-[loopback4-example-shopping/packages/shopping/src/application.ts](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/application.ts).
+In `src/application.ts`, bind the authentication components to your application
+class.
+
+{% include code-caption.html content="src/application.ts" %}
```ts
+// ---------- ADD IMPORTS -------------
import {AuthenticationComponent} from '@loopback/authentication';
+import {
+ JWTAuthenticationComponent,
+ SECURITY_SCHEME_SPEC,
+ UserServiceBindings,
+} from '@loopback/authentication-jwt';
+import {DbDataSource} from './datasources';
+// ------------------------------------
-export class ShoppingApplication extends BootMixin(
+export class TodoListApplication extends BootMixin(
ServiceMixin(RepositoryMixin(RestApplication)),
) {
- constructor(options?: ApplicationConfig) {
- super(options);
-
- // ...
-
- // Bind authentication component related elements
+ 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 -------------
}
- // ...
}
```
-### Securing an Endpoint with the Authentication Decorator
-
-Securing your application's API endpoints is done by decorating controller
-functions with the
-[Authentication Decorator](../../decorators/Decorators_authenticate.md).
-
-The decorator's syntax is:
-
-```ts
-@authenticate(strategyName: string, options?: object)
-```
+## Step 2: Add Authenticate Action
-In the `loopback4-example-shopping` application, there is only one endpoint that
-is secured.
+Next, we will add the authenticate action in the Sequence. We’ll also modify the
+error when authentication fails to return status code 401.
-In the `UserController` class in the
-[loopback4-example-shopping/packages/shopping/src/controllers/user.controller.ts](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/controllers/user.controller.ts),
-a user can print out his/her user profile by performing a `GET` request on the
-`/users/me` endpoint which is handled by the `printCurrentUser()` function.
+{% include code-caption.html content="/src/sequence.ts" %}
```ts
- // ...
-
- @get('/users/me', {
- responses: {
- '200': {
- description: 'The current user profile',
- content: {
- 'application/json': {
- schema: UserProfileSchema,
- },
- },
- },
- },
- })
- @authenticate('jwt')
- async printCurrentUser(
- @inject(SecurityBindings.USER)
- currentUserProfile: UserProfile,
- ): Promise {
- currentUserProfile.id = currentUserProfile[securityId];
- delete currentUserProfile[securityId];
- return currentUserProfile;
- }
-
- // ...
-```
-
-{% include note.html content="Since this controller method is obtaining SecurityBindings.USER via [method injection](../../Dependency-injection.md#method-injection) (instead of [constructor injection](../../Dependency-injection.md#constructor-injection)) and this method is decorated with the @authenticate decorator, there is no need to specify @inject(SecurityBindings.USER, {optional:true}). See [Using the Authentication Decorator](../../Loopback-component-authentication.md#using-the-authentication-decorator) for details.
-" %}
-
-The `/users/me` endpoint is decorated with
-
-```ts
-@authenticate('jwt')
-```
-
-and authentication will only succeed if a valid JWT token is provided in the
-`Authorization` header of the request.
-
-Basically, the
-[AuthenticateFn](https://github.com/strongloop/loopback-next/blob/master/packages/authentication/src/providers/auth-action.provider.ts)
-action in the custom sequence `MyAuthenticationSequence` (discussed in a later
-section) asks
-[AuthenticationStrategyProvider](https://github.com/strongloop/loopback-next/blob/master/packages/authentication/src/providers/auth-strategy.provider.ts)
-to resolve the registered authentication strategy with the name `'jwt'` (which
-is `JWTAuthenticationStrategy` and discussed in a later section). Then
-`AuthenticateFn` calls `JWTAuthenticationStrategy`'s `authenticate(request)`
-function to authenticate the request.
-
-If the provided JWT token is valid, then `JWTAuthenticationStrategy`'s
-`authenticate(request)` function returns the user profile. `AuthenticateFn` then
-places the user profile on the request context using the `SecurityBindings.USER`
-binding key. The user profile is available to the `printCurrentUser()`
-controller function in a variable `currentUserProfile: UserProfile` through
-dependency injection via the same `SecurityBindings.USER` binding key. The user
-profile is returned in the response.
-
-If the JWT token is missing/expired/invalid, then `JWTAuthenticationStrategy`'s
-`authenticate(request)` function fails and an
-[HTTP 401 UnAuthorized](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401)
-is thrown.
-
-If an **unknown** authentication strategy **name** is specified in the
-`@authenticate` decorator:
-
-```ts
-@authenticate('unknown')
-```
-
-then
-[AuthenticationStrategyProvider](https://github.com/strongloop/loopback-next/blob/master/packages/authentication/src/providers/auth-strategy.provider.ts)'s
-`findAuthenticationStrategy(name: string)` function cannot find a registered
-authentication strategy by that name, and an
-[HTTP 401 UnAuthorized](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401)
-is thrown.
-
-So, be sure to specify the correct authentication strategy name when decorating
-your endpoints with the `@authenticate` decorator.
-
-### Creating a Custom Sequence and Adding the Authentication Action
-
-In a LoopBack 4 application with REST API endpoints, each request passes through
-a stateless grouping of actions called a [Sequence](../../Sequence.md).
-
-Authentication is **not** part of the default sequence of actions, so you must
-create a custom sequence and add the authentication action.
-
-The custom sequence `MyAuthenticationSequence` in
-[loopback4-example-shopping/packages/shopping/src/sequence.ts](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/sequence.ts)
-implements the
-[SequenceHandler](https://github.com/strongloop/loopback-next/blob/master/packages/rest/src/sequence.ts)
-interface.
-
-```ts
-export class MyAuthenticationSequence implements SequenceHandler {
+// ---------- ADD IMPORTS -------------
+import {
+ AuthenticateFn,
+ AuthenticationBindings,
+ AUTHENTICATION_STRATEGY_NOT_FOUND,
+ USER_PROFILE_NOT_FOUND,
+} from '@loopback/authentication';
+// ------------------------------------
+export class MySequence implements SequenceHandler {
constructor(
- @inject(SequenceActions.FIND_ROUTE) protected findRoute: FindRoute,
- @inject(SequenceActions.PARSE_PARAMS)
- protected parseParams: ParseParams,
- @inject(SequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod,
- @inject(SequenceActions.SEND) protected send: Send,
- @inject(SequenceActions.REJECT) protected reject: Reject,
+ // ---- ADD THIS LINE ------
@inject(AuthenticationBindings.AUTH_ACTION)
protected authenticateRequest: AuthenticateFn,
) {}
-
async handle(context: RequestContext) {
try {
const {request, response} = context;
const route = this.findRoute(request);
-
- //call authentication action
+ // - enable jwt auth -
+ // call authentication action
+ // ---------- ADD THIS LINE -------------
await this.authenticateRequest(request);
-
- // Authentication successful, proceed to invoke controller
const args = await this.parseParams(request, route);
const result = await this.invoke(route, args);
this.send(response, result);
- } catch (error) {
- //
- // The authentication action utilizes a strategy resolver to find
- // an authentication strategy by name, and then it calls
- // strategy.authenticate(request).
- //
- // The strategy resolver throws a non-http error if it cannot
- // resolve the strategy. When the strategy resolver obtains
- // a strategy, it calls strategy.authenticate(request) which
- // is expected to return a user profile. If the user profile
- // is undefined, then it throws a non-http error.
- //
- // It is necessary to catch these errors and add HTTP-specific status
- // code property.
- //
- // Errors thrown by the strategy implementations already come
- // with statusCode set.
- //
- // In the future, we want to improve `@loopback/rest` to provide
- // an extension point allowing `@loopback/authentication` to contribute
- // mappings from error codes to HTTP status codes, so that application
- // don't have to map codes themselves.
+ } catch (err) {
+ // ---------- ADD THIS SNIPPET -------------
+ // if error is coming from the JWT authentication extension
+ // make the statusCode 401
if (
- error.code === AUTHENTICATION_STRATEGY_NOT_FOUND ||
- error.code === USER_PROFILE_NOT_FOUND
+ err.code === AUTHENTICATION_STRATEGY_NOT_FOUND ||
+ err.code === USER_PROFILE_NOT_FOUND
) {
- Object.assign(error, {statusCode: 401 /* Unauthorized */});
+ Object.assign(err, {statusCode: 401 /* Unauthorized */});
}
-
- this.reject(context, error);
- return;
+ // ---------- END OF SNIPPET -------------
+ this.reject(context, err);
}
}
}
```
-The authentication action/function is injected via the
-`AuthenticationBindings.AUTH_ACTION` binding key, is given the name
-`authenticateRequest` and has the type
-[AuthenticateFn](https://github.com/strongloop/loopback-next/blob/master/packages/authentication/src/types.ts).
-
-Calling
-
-```ts
-await this.authenticateRequest(request);
-```
-
-before
-
-```ts
-// ...
-const result = await this.invoke(route, args);
-this.send(response, result);
-// ...
-```
-
-ensures that authentication has succeeded before a controller endpoint is
-reached.
+## Step 3: Create the UserController for login
-To add the custom sequence `MyAuthenticationSequence` in the application, we
-must code the following in
-[loopback4-example-shopping/packages/shopping/src/application.ts](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/application.ts):
+In the UserController, we are going to create three endpoints:
-```ts
-export class ShoppingApplication extends BootMixin(
- ServiceMixin(RepositoryMixin(RestApplication)),
-) {
- constructor(options?: ApplicationConfig) {
- super(options);
+- `/login` for users to provide credential to login
+- `/whoami` for showing who is the current user
+- `/signup` for users to sign up
- // ...
-
- // Set up the custom sequence
- this.sequence(MyAuthenticationSequence);
-
- // ...
- }
-}
-```
+### 3a. Create the UserController
-### Creating a Custom JWT Authentication Strategy
+Create the controller using `lb4 controller` command with the empty controller
+option.
-When creating a custom authentication strategy, it is necessary to implement the
-[AuthenticationStrategy](https://github.com/strongloop/loopback-next/blob/master/packages/authentication/src/types.ts)
-interface.
-
-A custom JWT authentication strategy `JWTAuthenticationStrategy` in
-[loopback4-example-shopping/packages/shopping/src/authentication-strategies/jwt-strategy.ts](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/authentication-strategies/jwt-strategy.ts)
-was implemented as follows:
-
-```ts
-import {inject} from '@loopback/context';
-import {HttpErrors, Request} from '@loopback/rest';
-import {AuthenticationStrategy, TokenService} from '@loopback/authentication';
-import {UserProfile} from '@loopback/security';
-
-import {TokenServiceBindings} from '../keys';
-
-export class JWTAuthenticationStrategy implements AuthenticationStrategy {
- name: string = 'jwt';
-
- constructor(
- @inject(TokenServiceBindings.TOKEN_SERVICE)
- public tokenService: TokenService,
- ) {}
-
- async authenticate(request: Request): Promise {
- const token: string = this.extractCredentials(request);
- const userProfile: UserProfile = await this.tokenService.verifyToken(token);
- return userProfile;
- }
-
- extractCredentials(request: Request): string {
- if (!request.headers.authorization) {
- throw new HttpErrors.Unauthorized(`Authorization header not found.`);
- }
-
- // for example: Bearer xxx.yyy.zzz
- const authHeaderValue = request.headers.authorization;
+```sh
+$ lb4 controller
+? Controller class name: User
+Controller User will be created in src/controllers/user.controller.ts
- if (!authHeaderValue.startsWith('Bearer')) {
- throw new HttpErrors.Unauthorized(
- `Authorization header is not of type 'Bearer'.`,
- );
- }
+? What kind of controller would you like to generate? Empty Controller
- //split the string into 2 parts: 'Bearer ' and the `xxx.yyy.zzz`
- const parts = authHeaderValue.split(' ');
- if (parts.length !== 2)
- throw new HttpErrors.Unauthorized(
- `Authorization header value has too many parts. It must follow the pattern: 'Bearer xx.yy.zz' where xx.yy.zz is a valid JWT token.`,
- );
- const token = parts[1];
+create src/controllers/user.controller.ts
+ update src/controllers/index.ts
- return token;
- }
-}
+Controller User was created in src/controllers/
```
-It has a **name** `'jwt'`, and it implements the
-`async authenticate(request: Request): Promise`
-function.
-
-An extra function `extractCredentials(request: Request): string` was added to
-extract the JWT token from the request. This authentication strategy expects
-every request to pass a valid JWT token in the `Authorization` header.
-
-`JWTAuthenticationStrategy` also makes use of a token service `tokenService` of
-type `TokenService` that is injected via the
-`TokenServiceBindings.TOKEN_SERVICE` binding key. It is used to verify the
-validity of the JWT token and return a user profile.
-
-This token service is explained in a later section.
+### 3b. Add endpoints in UserController
-### Registering the Custom JWT Authentication Strategy
+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.
-To register the custom authentication strategy `JWTAuthenticationStrategy` with
-the **name** `'jwt'` as a part of the authentication framework, we need to code
-the following in
-[loopback4-example-shopping/packages/shopping/src/application.ts](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/application.ts).
+{% include code-caption.html content="/src/controllers/user.controller.ts" %}
```ts
-import {registerAuthenticationStrategy} from '@loopback/authentication';
-
-export class ShoppingApplication extends BootMixin(
- ServiceMixin(RepositoryMixin(RestApplication)),
-) {
- constructor(options?: ApplicationConfig) {
- super(options);
- // ...
- registerAuthenticationStrategy(this, JWTAuthenticationStrategy);
- // ...
- }
-}
-```
-
-### Creating a Token Service
-
-The token service `JWTService` in
-[loopback4-example-shopping/packages/shopping/src/services/jwt-service.ts](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/services/jwt-service.ts)
-implements an **optional** helper
-[TokenService](https://github.com/strongloop/loopback-next/blob/master/packages/authentication/src/services/token.service.ts)
-interface.
-
-```ts
-import {inject} from '@loopback/context';
-import {HttpErrors} from '@loopback/rest';
-import {promisify} from 'util';
+// ---------- ADD IMPORTS -------------
+import {inject} from '@loopback/core';
+import {
+ TokenServiceBindings,
+ MyUserService,
+ UserServiceBindings,
+ UserRepository,
+} from '@loopback/authentication-jwt';
import {TokenService} from '@loopback/authentication';
-import {UserProfile} from '@loopback/security';
-import {TokenServiceBindings} from '../keys';
-
-const jwt = require('jsonwebtoken');
-const signAsync = promisify(jwt.sign);
-const verifyAsync = promisify(jwt.verify);
-
-export class JWTService implements TokenService {
- constructor(
- @inject(TokenServiceBindings.TOKEN_SECRET)
- private jwtSecret: string,
- @inject(TokenServiceBindings.TOKEN_EXPIRES_IN)
- private jwtExpiresIn: string,
- ) {}
-
- async verifyToken(token: string): Promise {
- if (!token) {
- throw new HttpErrors.Unauthorized(
- `Error verifying token: 'token' is null`,
- );
- }
-
- let userProfile: UserProfile;
-
- try {
- // decode user profile from token
- const decryptedToken = await verifyAsync(token, this.jwtSecret);
- // don't copy over token field 'iat' and 'exp', nor 'email' to user profile
- userProfile = Object.assign(
- {id: '', name: ''},
- {id: decryptedToken.id, name: decryptedToken.name},
- );
- } catch (error) {
- throw new HttpErrors.Unauthorized(
- `Error verifying token: ${error.message}`,
- );
- }
-
- return userProfile;
- }
-
- async generateToken(userProfile: UserProfile): Promise {
- if (!userProfile) {
- throw new HttpErrors.Unauthorized(
- 'Error generating token: userProfile is null',
- );
- }
-
- // Generate a JSON Web Token
- let token: string;
- try {
- token = await signAsync(userProfile, this.jwtSecret, {
- expiresIn: Number(this.jwtExpiresIn),
- });
- } catch (error) {
- throw new HttpErrors.Unauthorized(`Error encoding token: ${error}`);
- }
-
- return token;
- }
-}
-```
-
-`JWTService` generates or verifies JWT tokens using the `sign` and `verify`
-functions of [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken).
-
-It makes use of `jwtSecret` and `jwtExpiresIn` **string** values that are
-injected via the `TokenServiceBindings.TOKEN_SECRET` and the
-`TokenServiceBindings.TOKEN_EXPIRES_IN` binding keys respectively.
-
-The `async generateToken(userProfile: UserProfile): Promise` function
-takes in a user profile of type
-[UserProfile](https://github.com/strongloop/loopback-next/blob/master/packages/security/src/types.ts),
-generates a JWT token of type `string` using: the **user profile** as the
-payload, **jwtSecret** and **jwtExpiresIn**.
-
-The `async verifyToken(token: string): Promise` function takes in a
-JWT token of type `string`, verifies the JWT token, and returns the payload of
-the token which is a user profile of type `UserProfile`.
-
-To bind the JWT `secret`, `expires in` values and the `JWTService` class to
-binding keys, we need to code the following in
-[loopback4-example-shopping/packages/shopping/src/application.ts](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/application.ts):
-
-```ts
-export class ShoppingApplication extends BootMixin(
- ServiceMixin(RepositoryMixin(RestApplication)),
-) {
- constructor(options?: ApplicationConfig) {
- super(options);
+import {SecurityBindings, UserProfile} from '@loopback/security';
+import {repository} from '@loopback/repository';
+// ----------------------------------
- // ...
- this.setUpBindings();
- // ...
- }
-
- setUpBindings(): void {
- // ...
-
- this.bind(TokenServiceBindings.TOKEN_SECRET).to(
- TokenServiceConstants.TOKEN_SECRET_VALUE,
- );
-
- this.bind(TokenServiceBindings.TOKEN_EXPIRES_IN).to(
- TokenServiceConstants.TOKEN_EXPIRES_IN_VALUE,
- );
-
- this.bind(TokenServiceBindings.TOKEN_SERVICE).toClass(JWTService);
-
- // ...
- }
-}
-```
-
-In the code above, `TOKEN_SECRET_VALUE` has a value of `'myjwts3cr3t'` and
-`TOKEN_EXPIRES_IN_VALUE` has a value of `'600'`.
-
-`JWTService` is used in two places within the application:
-`JWTAuthenticationStrategy` in
-[loopback4-example-shopping/packages/shopping/src/authentication-strategies/jwt-strategy.ts](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/authentication-strategies/jwt-strategy.ts),
-and `UserController` in
-[loopback4-example-shopping/packages/shopping/src/controllers/user.controller.ts](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/controllers/user.controller.ts).
-
-### Creating a User Service
-
-The user service `MyUserService` in
-[loopback4-example-shopping/packages/shopping/src/services/user-service.ts](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/services/user-service.ts)
-implements an **optional** helper
-[UserService](https://github.com/strongloop/loopback-next/blob/master/packages/authentication/src/services/user.service.ts)
-interface.
-
-```ts
-export class MyUserService implements UserService {
- constructor(
- @repository(UserRepository) public userRepository: UserRepository,
- @inject(PasswordHasherBindings.PASSWORD_HASHER)
- public passwordHasher: PasswordHasher,
- ) {}
-
- async verifyCredentials(credentials: Credentials): Promise {
- const foundUser = await this.userRepository.findOne({
- where: {email: credentials.email},
- });
-
- if (!foundUser) {
- throw new HttpErrors.NotFound(
- `User with email ${credentials.email} not found.`,
- );
- }
- const passwordMatched = await this.passwordHasher.comparePassword(
- credentials.password,
- foundUser.password,
- );
-
- if (!passwordMatched) {
- throw new HttpErrors.Unauthorized('The credentials are not correct.');
- }
-
- return foundUser;
- }
-
- convertToUserProfile(user: User): UserProfile {
- // since first name and lastName are optional, no error is thrown if not provided
- let userName = '';
- if (user.firstName) userName = `${user.firstName}`;
- if (user.lastName)
- userName = user.firstName
- ? `${userName} ${user.lastName}`
- : `${user.lastName}`;
- return {id: user.id, name: userName};
- }
-}
-```
-
-The `async verifyCredentials(credentials: Credentials): Promise` function
-takes in a credentials of type
-[Credentials](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/repositories/user.repository.ts),
-and returns a **user** of type
-[User](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/models/user.model.ts).
-It searches through an injected user repository of type `UserRepository`.
-
-The `convertToUserProfile(user: User): UserProfile` function takes in a **user**
-of type `User` and returns a user profile of type
-[UserProfile](https://github.com/strongloop/loopback-next/blob/master/packages/security/src/types.ts).
-A user profile, in this case, is the minimum set of user properties which
-identify an authenticated user.
-
-`MyUserService` is used in by `UserController` in
-[loopback4-example-shopping/packages/shopping/src/controllers/user.controller.ts](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/controllers/user.controller.ts).
-
-To bind the `MyUserService` class, and the password hashing utility it uses, to
-binding keys, we need to code the following in
-[loopback4-example-shopping/packages/shopping/src/application.ts](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/application.ts):
-
-```ts
-export class ShoppingApplication extends BootMixin(
- ServiceMixin(RepositoryMixin(RestApplication)),
-) {
- constructor(options?: ApplicationConfig) {
- super(options);
-
- // ...
-
- this.setUpBindings();
-
- // ...
- }
-
- setUpBindings(): void {
- // ...
-
- // Bind bcrypt hash services - utilized by 'UserController' and 'MyUserService'
- this.bind(PasswordHasherBindings.ROUNDS).to(10);
- this.bind(PasswordHasherBindings.PASSWORD_HASHER).toClass(BcryptHasher);
-
- this.bind(UserServiceBindings.USER_SERVICE).toClass(MyUserService);
-
- // ...
- }
-}
-```
-
-### Adding Users
-
-In the `UserController` class in the
-[loopback4-example-shopping/packages/shopping/src/controllers/user.controller.ts](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/controllers/user.controller.ts),
-users can be added by performing a `POST` request to the `/users` endpoint which
-is handled by the `create()` function.
-
-Because user credentials like password are stored outside of the main user
-profile, we need to create a new model (a
-[Data Transfer Object](https://en.wikipedia.org/wiki/Data_transfer_object)) to
-describe data required to create a new user. The class inherits from `User`
-model to include all user profile properties, and adds a new property `password`
-allowing clients to specify the password too.
-
-```ts
-@model()
-export class NewUserRequest extends User {
- @property({
- type: 'string',
- required: true,
- })
- password: string;
-}
-```
-
-The controller method `UserController.create` then has to remove additional
-properties like `password` before passing the data to Repository (and database).
-
-```ts
-export class UserController {
- constructor(
- // ...
- @repository(UserRepository) public userRepository: UserRepository,
- @inject(PasswordHasherBindings.PASSWORD_HASHER)
- public passwordHasher: PasswordHasher,
+constructor(
@inject(TokenServiceBindings.TOKEN_SERVICE)
public jwtService: TokenService,
@inject(UserServiceBindings.USER_SERVICE)
- public userService: UserService,
+ public userService: MyUserService,
+ @inject(SecurityBindings.USER, {optional: true})
+ public user: UserProfile,
+ @repository(UserRepository) protected userRepository: UserRepository,
) {}
-
- // ...
-
- @post('/users')
- async create(
- @requestBody({
- content: {
- 'application/json': {
- schema: getModelSchemaRef(NewUserRequest, {
- title: 'NewUser',
- }),
- },
- },
- })
- newUserRequest: NewUserRequest,
- ): Promise {
- // ensure a valid email value and password value
- validateCredentials(_.pick(newUserRequest, ['email', 'password']));
-
- // encrypt the password
- const password = await this.passwordHasher.hashPassword(
- newUserRequest.password,
- );
-
- // create the new user
- const savedUser = await this.userRepository.create(
- _.omit(newUserRequest, 'password'),
- );
-
- // set the password
- await this.userRepository
- .userCredentials(savedUser.id)
- .create({password});
-
- return savedUser;
- }
-
- // ...
```
-A user of type
-[User](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/models/user.model.ts)
-is added to the database via the user repository if the user's email and
-password values are in an acceptable format.
+For the implementation of all the 3 endpoints, you can take a look at this
+[user.controller.ts](https://github.com/strongloop/loopback-next/blob/master/examples/todo-jwt/src/controllers/user.controller.ts)
+in the [`todo-jwt` example].
-### Issuing a JWT Token on Successful Login
+## Step 4: Protect the Todo APIs
-In the `UserController` class in the
-[loopback4-example-shopping/packages/shopping/src/controllers/user.controller.ts](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/controllers/user.controller.ts),
-a user can `log in` by performing a `POST` request, containing an `email` and
-`password`, to the `/users/login` endpoint which is handled by the `login()`
-function.
+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.
-```ts
-export class UserController {
- constructor(
- // ...
- @repository(UserRepository) public userRepository: UserRepository,
- @inject(PasswordHasherBindings.PASSWORD_HASHER)
- public passwordHasher: PasswordHasher,
- @inject(TokenServiceBindings.TOKEN_SERVICE)
- public jwtService: TokenService,
- @inject(UserServiceBindings.USER_SERVICE)
- public userService: UserService,
- ) {}
-
- // ...
-
- @post('/users/login', {
- responses: {
- '200': {
- description: 'Token',
- content: {
- 'application/json': {
- schema: {
- type: 'object',
- properties: {
- token: {
- type: 'string',
- },
- },
- },
- },
- },
- },
- },
- })
- async login(
- @requestBody(CredentialsRequestBody) credentials: Credentials,
- ): Promise<{token: string}> {
- // ensure the user exists, and the password is correct
- const user = await this.userService.verifyCredentials(credentials);
-
- // convert a User object into a UserProfile object (reduced set of properties)
- const userProfile = this.userService.convertToUserProfile(user);
-
- // create a JSON Web Token based on the user profile
- const token = await this.jwtService.generateToken(userProfile);
-
- return {token};
- }
-}
-```
-
-The user service returns a user object when the email and password are verified
-as valid; otherwise it throws an
-[HTTP 401 UnAuthorized](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401).
-The user service is then called to create a slimmer user profile from the user
-object. Then this user profile is used as the payload of the JWT token created
-by the token service. The token is returned in the response.
-
-### Specifying the Security Settings in the OpenAPI Specification
-
-In the shopping cart application, only one endpoint, `GET /users/me` is secured
-with a custom JWT authentication strategy. In order to be able to `set` and
-`use` a JWT token in the `API Explorer` (as opposed to using a REST API client),
-it is necessary to specify
-[security scheme object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#security-scheme-object)
-and
-[security requirement object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#securityRequirementObject)
-information in the application's OpenAPI specification.
-
-In
-[loopback4-example-shopping/packages/shopping/src/utils/security-spec.ts](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/utils/security-spec.ts)
-we defined the following:
+{% include code-caption.html content="/src/controllers/user.controller.ts" %}
```ts
-import {SecuritySchemeObject, ReferenceObject} from '@loopback/openapi-v3';
-
-export const OPERATION_SECURITY_SPEC = [{bearerAuth: []}];
-export type SecuritySchemeObjects = {
- [securityScheme: string]: SecuritySchemeObject | ReferenceObject;
-};
-export const SECURITY_SCHEME_SPEC: SecuritySchemeObjects = {
- bearerAuth: {
- type: 'http',
- scheme: 'bearer',
- bearerFormat: 'JWT',
- },
-};
-```
-
-`SECURITY_SCHEME_SPEC` is a map of security scheme object definitions that are
-defined globally for the application. For our purposes, it only contains a
-single security scheme object that contains the
-[bearerAuth](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#jwt-bearer-sample)
-definition.
-
-`OPERATION_SECURITY_SPEC` is an **operation-level**
-[security requirement object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#securityRequirementObject)
-that references the `bearerAuth` security scheme object definition. It is used
-by the `/users/me` endpoint in
-[loopback4-example-shopping/packages/shopping/src/controllers/user.controller.ts](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/controllers/user.controller.ts).
-
-Notice the line
-
-```
-security: OPERATION_SECURITY_SPEC,
-```
-
-in the code below:
-
-```ts
-@get('/users/me', {
- security: OPERATION_SECURITY_SPEC,
- responses: {
- '200': {
- description: 'The current user profile',
- content: {
- 'application/json': {
- schema: UserProfileSchema,
- },
- },
- },
- },
-})
-@authenticate('jwt')
-async printCurrentUser(
- @inject(SecurityBindings.USER)
- currentUserProfile: UserProfile,
-): Promise {
- currentUserProfile.id = currentUserProfile[securityId];
- delete currentUserProfile[securityId];
- return currentUserProfile;
+// ---------- ADD IMPORTS -------------
+import {authenticate} from '@loopback/authentication';
+// ------------------------------------
+@authenticate('jwt') // <---- Apply the @authenticate decorator at the class level
+export class TodoController {
+ //...
}
```
-[loopback4-example-shopping/packages/shopping/src/application.ts](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/application.ts)
-contributes the `security scheme object` definitions to the OpenAPI
-specification in the following manner:
-
-```ts
-import {SECURITY_SCHEME_SPEC} from './utils/security-spec';
+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.
-export class ShoppingApplication extends BootMixin(
- ServiceMixin(RepositoryMixin(RestApplication)),
-) {
- constructor(options?: ApplicationConfig) {
- super(options);
-
- this.api({
- openapi: '3.0.0',
- info: {title: pkg.name, version: pkg.version},
- paths: {},
- components: {securitySchemes: SECURITY_SCHEME_SPEC},
- servers: [{url: '/'}],
- });
-// ...
-```
-
-Later, when you visit
-[http://[::1]:3000/openapi.json](http://[::1]:3000/openapi.json) while the
-application is running, search for the text `bearerAuth`. You should find these
-two occurrences:
-
-```json
-"components": {
- "securitySchemes": {
- "bearerAuth": {
- "type": "http",
- "scheme": "bearer",
- "bearerFormat": "JWT"
- }
- },
-```
-
-and
-
-```json
-"/users/me": {
- "get": {
- "x-controller-name": "UserController",
- "x-operation-name": "printCurrentUser",
- "tags": [
- "UserController"
- ],
- "security": [
- {
- "bearerAuth": []
- }
- ],
-```
-
-Later, when you visit [http://[::1]:3000/explorer/](http://[::1]:3000/explorer/)
-while the application is running, you should see an `Authorize` button at the
-top.
-
-
-
-as well as a **lock** icon on the `GET /users/me` endpoint in the
-`UserController` section
-
-
-
-### How to Specify A Single OpenAPI Specification Security Requirement Object For All Endpoints
-
-Currently, the `loopback4-example-shopping` application does not implement this,
-but there is a way to specify the same OpenAPI specification security
-requirement object to **all** endpoints of your application.
-
-The security scheme object definition is still defined in `components` section,
-but the `security` property value is set at the **top** level instead of the
-**operation** level.
-
-```json
-"components": {
- "securitySchemes": {
- "bearerAuth": {
- "type": "http",
- "scheme": "bearer",
- "bearerFormat": "JWT"
- }
- },
- // ...
-},
-"security": [
- {
- "bearerAuth": []
- }
- ]
-```
-
-To accomplish this, we only need to make some minor changes to the code examples
-provided earlier.
-
-In
-[loopback4-example-shopping/packages/shopping/src/utils/security-spec.ts](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/utils/security-spec.ts),
-we simply rename `OPERATION_SECURITY_SPEC` to `SECURITY_SPEC`.
-
-```ts
-import {SecuritySchemeObject, ReferenceObject} from '@loopback/openapi-v3';
-
-export const SECURITY_SPEC = [{bearerAuth: []}];
-export type SecuritySchemeObjects = {
- [securityScheme: string]: SecuritySchemeObject | ReferenceObject;
-};
-export const SECURITY_SCHEME_SPEC: SecuritySchemeObjects = {
- bearerAuth: {
- type: 'http',
- scheme: 'bearer',
- bearerFormat: 'JWT',
- },
-};
-```
-
-In
-[loopback4-example-shopping/packages/shopping/src/controllers/user.controller.ts](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/controllers/user.controller.ts),
-we remove the line:
-
-```
-security: SECURITY_SPEC_OPERATION,
-```
-
-from the `/users/me` endpoint:
-
-```ts
-@get('/users/me', {
- responses: {
- '200': {
- description: 'The current user profile',
- content: {
- 'application/json': {
- schema: UserProfileSchema,
- },
- },
- },
- },
-})
-@authenticate('jwt')
-async printCurrentUser(
- @inject(SecurityBindings.USER)
- currentUserProfile: UserProfile,
-): Promise {
- currentUserProfile.id = currentUserProfile[securityId];
- delete currentUserProfile[securityId];
- return currentUserProfile;
-}
-```
-
-In
-[loopback4-example-shopping/packages/shopping/src/application.ts](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/application.ts),
-we simply add the line
-
-```
-security: SECURITY_SPEC
-```
-
-to the call to `this.api({...})`. This basically means the security requirement
-object definition, `bearerAuth`, will be applied to all endpoints.
-
-```ts
-import {SECURITY_SCHEME_SPEC, SECURITY_SPEC} from './utils/security-spec';
-
-export class ShoppingApplication extends BootMixin(
- ServiceMixin(RepositoryMixin(RestApplication)),
-) {
- constructor(options?: ApplicationConfig) {
- super(options);
-
- this.api({
- openapi: '3.0.0',
- info: {title: pkg.name, version: pkg.version},
- paths: {},
- components: {securitySchemes: SECURITY_SCHEME_SPEC},
- servers: [{url: '/'}],
- security: SECURITY_SPEC
- });
-// ...
-```
-
-Visiting [http://[::1]:3000/explorer/](http://[::1]:3000/explorer/) while the
-application is running, you should still see an `Authorize` button at the top as
-before.
-
-
-
-But now, **all** the endpoints have the lock icon.
-
-
-
-This means that you can set the JWT token once via the
-`Authorize button/dialog`, and the token will be available to all the endpoints
-your interact with.
-
-There are plans to allow contributions to the OpenAPI specification via an
-extensionPoint/extensions pattern (
-[Issue #3854](https://github.com/strongloop/loopback-next/issues/3854) );
-including having authentication strategies automatically contribute security
-scheme/requirement object information (
-[Issue #3669](https://github.com/strongloop/loopback-next/issues/3669) ).
-
-### Summary
-
-We've gone through the steps that were used to add JWT `authentication` to the
-`loopback4-example-shopping` application, and add
-`security scheme/requirement object` settings to its OpenAPI specification.
-
-The final `ShoppingApplication` class in
-[loopback4-example-shopping/packages/shopping/src/application.ts](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/application.ts)
-should look like this:
-
-```ts
-import {BootMixin} from '@loopback/boot';
-import {ApplicationConfig, BindingKey} from '@loopback/core';
-import {RepositoryMixin} from '@loopback/repository';
-import {RestApplication} from '@loopback/rest';
-import {ServiceMixin} from '@loopback/service-proxy';
-import {MyAuthenticationSequence} from './sequence';
-import {
- RestExplorerBindings,
- RestExplorerComponent,
-} from '@loopback/rest-explorer';
-import {
- TokenServiceBindings,
- UserServiceBindings,
- TokenServiceConstants,
-} from './keys';
-import {JWTService} from './services/jwt-service';
-import {MyUserService} from './services/user-service';
-
-import path from 'path';
-import {
- AuthenticationComponent,
- registerAuthenticationStrategy,
-} from '@loopback/authentication';
-import {PasswordHasherBindings} from './keys';
-import {BcryptHasher} from './services/hash.password.bcryptjs';
-import {JWTAuthenticationStrategy} from './authentication-strategies/jwt-strategy';
-import {SECURITY_SCHEME_SPEC} from './utils/security-spec';
-
-/**
- * Information from package.json
- */
-export interface PackageInfo {
- name: string;
- version: string;
- description: string;
-}
-export const PackageKey = BindingKey.create('application.package');
-
-const pkg: PackageInfo = require('../package.json');
-
-export class ShoppingApplication extends BootMixin(
- ServiceMixin(RepositoryMixin(RestApplication)),
-) {
- constructor(options?: ApplicationConfig) {
- super(options);
-
- this.api({
- openapi: '3.0.0',
- info: {title: pkg.name, version: pkg.version},
- paths: {},
- components: {securitySchemes: SECURITY_SCHEME_SPEC},
- servers: [{url: '/'}],
- });
-
- this.setUpBindings();
-
- // Bind authentication component related elements
- this.component(AuthenticationComponent);
-
- registerAuthenticationStrategy(this, JWTAuthenticationStrategy);
-
- // Set up the custom sequence
- this.sequence(MyAuthenticationSequence);
-
- // Set up default home page
- this.static('/', path.join(__dirname, '../public'));
-
- // Customize @loopback/rest-explorer configuration here
- this.configure(RestExplorerBindings.COMPONENT).to({
- path: '/explorer',
- });
- this.component(RestExplorerComponent);
-
- this.projectRoot = __dirname;
- // Customize @loopback/boot Booter Conventions here
- this.bootOptions = {
- controllers: {
- // Customize ControllerBooter Conventions here
- dirs: ['controllers'],
- extensions: ['.controller.js'],
- nested: true,
- },
- };
- }
-
- setUpBindings(): void {
- // Bind package.json to the application context
- this.bind(PackageKey).to(pkg);
+## Try it out
- this.bind(TokenServiceBindings.TOKEN_SECRET).to(
- TokenServiceConstants.TOKEN_SECRET_VALUE,
- );
+Start the application by running npm start and go to
+http://localhost:3000/explorer. You’ll see the 3 new endpoints under
+`UserController` together with the other endpoints under `TodoController`.
- this.bind(TokenServiceBindings.TOKEN_EXPIRES_IN).to(
- TokenServiceConstants.TOKEN_EXPIRES_IN_VALUE,
- );
+
- this.bind(TokenServiceBindings.TOKEN_SERVICE).toClass(JWTService);
+1. Sign up using the `/signup` API
- // // Bind bcrypt hash services
- this.bind(PasswordHasherBindings.ROUNDS).to(10);
- this.bind(PasswordHasherBindings.PASSWORD_HASHER).toClass(BcryptHasher);
+ Since we don’t have any users created, click on `POST /signup`. For the
+ requestBody, the minimum you need is `email` and `password`. i.e.
- this.bind(UserServiceBindings.USER_SERVICE).toClass(MyUserService);
- }
-}
-```
+ ```json
+ {
+ "email": "testuser2@abc.com",
+ "password": "testuser2"
+ }
+ ```
-## Running the Completed Application
+2. Log in using the `POST /users/login` API
-To run the completed application, follow the instructions in the
-[Try it out](#try-it-out) section.
+ After calling `/users/login`, the response body will look something like:
-For more information, please visit
-[Authentication Component](../../Loopback-component-authentication.md).
+ ```json
+ {
+ "token": "aaaaaaaaa.aaaaaaaaaaaaaaaaa"
+ }
+ ```
-## Bugs/Feedback
+ Copy the token. Go to the top of the API Explorer, click the “Authorize”
+ button.
-Open an issue in
-[loopback4-example-shopping](https://github.com/strongloop/loopback4-example-shopping)
-and we'll take a look!
+ 
-## Contributions
+ Paste the token that you previously copied to the “Value” field and then
+ click Authorize.
-- [Guidelines](https://github.com/strongloop/loopback-next/blob/master/docs/CONTRIBUTING.md)
-- [Join the team](https://github.com/strongloop/loopback-next/issues/110)
+ 
-## Tests
+ In the future API calls, this token will be added to the `Authorization`
+ header .
-Run `npm test` from the root folder.
+3. Get all todos using `GET /todos` API You should be able to call this API
+ successfully.
-## Contributors
+## Conclusion
-See
-[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors).
+Congratulations! You have successfully added the JWT authentication to the
+LoopBack application! We did the following:
-## License
+- 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
-MIT
+See the
+[todo-jwt example](https://github.com/strongloop/loopback-next/blob/master/examples/todo-jwt)
+for the working application. You can also run `lb4 example todo-jwt` to download
+the example.