From 3410c5645e48a64813062b151a1c3b8642149ee2 Mon Sep 17 00:00:00 2001 From: Dominique Emond Date: Tue, 4 Jun 2019 12:07:38 -0400 Subject: [PATCH] docs: add a tutorial about authentication Add a tutorial about authentication. Instructions to download and run the completed loopback4-example-shopping application, and an explanation of how authentication was added to the loopback4-example-shopping application. --- docs/site/imgs/json_web_token_overview.png | Bin 0 -> 81120 bytes docs/site/sidebars/lb4_sidebar.yml | 4 + .../authentication/Authentication-Tutorial.md | 974 ++++++++++++++++++ 3 files changed, 978 insertions(+) create mode 100644 docs/site/imgs/json_web_token_overview.png create mode 100644 docs/site/tutorials/authentication/Authentication-Tutorial.md diff --git a/docs/site/imgs/json_web_token_overview.png b/docs/site/imgs/json_web_token_overview.png new file mode 100644 index 0000000000000000000000000000000000000000..a780e2e05f3624e3f3c8dc04ea20eaa17eb0e069 GIT binary patch literal 81120 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�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 HG~oXN)5h6m literal 0 HcmV?d00001 diff --git a/docs/site/sidebars/lb4_sidebar.yml b/docs/site/sidebars/lb4_sidebar.yml index 8df4fbbe3156..268aa868914d 100644 --- a/docs/site/sidebars/lb4_sidebar.yml +++ b/docs/site/sidebars/lb4_sidebar.yml @@ -107,6 +107,10 @@ children: url: express-with-lb4-rest-tutorial.html output: 'web, pdf' + - title: 'How to secure your LoopBack 4 application with JWT authentication' + url: Authentication-Tutorial.html + output: 'web, pdf' + - title: 'Examples' url: Examples.html output: 'web, pdf' diff --git a/docs/site/tutorials/authentication/Authentication-Tutorial.md b/docs/site/tutorials/authentication/Authentication-Tutorial.md new file mode 100644 index 000000000000..d10c96c2cfcd --- /dev/null +++ b/docs/site/tutorials/authentication/Authentication-Tutorial.md @@ -0,0 +1,974 @@ +--- +lang: en +title: 'How to secure your LoopBack 4 application with JWT authentication' +keywords: LoopBack 4.0, LoopBack 4, Authentication, Tutorial +sidebar: lb4_sidebar +permalink: /doc/en/lb4/Authentication-Tutorial.html +summary: A LoopBack 4 application that uses JWT authentication +--- + +## 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 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. + +Here is a brief summary of the `JSON Web Token (JWT)` approach. + +![JSON Web Token Authentication Overview](../../imgs/json_web_token_overview.png) + +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**. +Each part is encrypted using a **secret**, and the parts are separated by a +period. + +For example: + +```ts +// {encrypted-header}.{encrypted-payload}.{encrypted-signature} +eyJhbXVCJ9.eyJpZCI6Ij.I3wpRNCH4; +// actual parts have been reduced in size for viewing purposes +``` + +{% include note.html content="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)](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 git@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 + ``` + +2. 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`. + +3. In the `UserController` section, click on `POST /users`, click on + `'Try it out'`, specify: + + ```ts + { + "id": "1", + "email": "user1@example.com", + "password": "thel0ngp@55w0rd", + "firstName": "User", + "lastName": "One" + } + ``` + + and click on `'Execute'` to **add** a new user named `'User One'`. + +4. In the `UserController` section, click on `POST /users/login`, click on + `'Try it out'`, specify: + + ```ts + { + "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: + + ```ts + { + "token": "some.token.value" + } + ``` + +5. Perform a `GET` request on the secured endpoint `/users/me` making sure to + provide the JWT token in the `Authorization` header. If authentication + succeeds, the + [user profile](https://github.com/strongloop/loopback-next/blob/master/packages/authentication/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. + + ```sh + curl -X GET \ + --header 'Authorization: Bearer some.token.value' \ + http://127.0.0.1:3000/users/me + ``` + + The response is: + + ```sh + {"id":"1","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: + +```sh +npm install --save @loopback/authentication +``` + +### Adding the AuthenticationComponent to 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). + +```ts +import { + AuthenticationComponent +} from '@loopback/authentication'; + +export class ShoppingApplication extends BootMixin( + ServiceMixin(RepositoryMixin(RestApplication)), +) { + constructor(options?: ApplicationConfig) { + super(options); + + // ... + + // Bind authentication component related elements + this.component(AuthenticationComponent); + + // ... +``` + +### 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) +``` + +In the `loopback4-example-shopping` application, there is only one endpoint that +is secured. + +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. + +```ts + // ... + + @get('/users/me', { + responses: { + '200': { + description: 'The current user profile', + content: { + 'application/json': { + schema: UserProfileSchema, + }, + }, + }, + }, + }) + @authenticate('jwt') + async printCurrentUser( + @inject(AuthenticationBindings.CURRENT_USER) + currentUserProfile: UserProfile, + ): Promise { + return currentUserProfile; + } + + // ... +``` + +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 +`AuthenticationBindings.CURRENT_USER` binding key. The user profile is available +to the `printCurrentUser()` controller function in a variable +`currentUserProfile: UserProfile` through dependency injection via the same +`AuthenticationBindings.CURRENT_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` implements the +[SequenceHandler](https://github.com/strongloop/loopback-next/blob/master/packages/rest/src/sequence.ts) +interface. + +```ts +export class MyAuthenticationSequence 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, + @inject(AuthenticationBindings.AUTH_ACTION) + protected authenticateRequest: AuthenticateFn, + ) {} + + async handle(context: RequestContext) { + try { + const {request, response} = context; + const route = this.findRoute(request); + + //call authentication action + 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. + if ( + error.code === AUTHENTICATION_STRATEGY_NOT_FOUND || + error.code === USER_PROFILE_NOT_FOUND + ) { + Object.assign(error, {statusCode: 401 /* Unauthorized */}); + } + + this.reject(context, error); + return; + } + } +} +``` + +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. + +To add the custom sequence `MyAuthenticationSequence` in the application, we +must code the following in +`loopback4-example-shopping/packages/shopping/src/application.ts`: + +```ts +export class ShoppingApplication extends BootMixin( + ServiceMixin(RepositoryMixin(RestApplication)), +) { + constructor(options?: ApplicationConfig) { + super(options); + + // ... + + // Set up the custom sequence + this.sequence(MyAuthenticationSequence); + + // ... + } +} +``` + +### Creating a Custom JWT Authentication Strategy + +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, + UserProfile, + TokenService, +} from '@loopback/authentication'; +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; + + if (!authHeaderValue.startsWith('Bearer')) { + throw new HttpErrors.Unauthorized( + `Authorization header is not of type 'Bearer'.`, + ); + } + + //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]; + + return token; + } +} +``` + +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. + +### Registering the Custom JWT Authentication Strategy + +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`. + +```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` +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'; +import {TokenService, UserProfile} from '@loopback/authentication'; +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/authentication/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`: + +```ts +export class ShoppingApplication extends BootMixin( + ServiceMixin(RepositoryMixin(RestApplication)), +) { + constructor(options?: ApplicationConfig) { + super(options); + + // ... + 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`, +and `UserController` in +`loopback4-example-shopping/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` +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/authentication/src/types.ts). +A user profile, in this case, is the minimum set of user properties which +indentify an authenticated user. + +`MyUserService` is used in by `UserController` in +`loopback4-example-shopping/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`: + +```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`, +users can be added by performing a `POST` request to the `/users` endpoint which +is handled by the `create()` function. + +```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') + async create(@requestBody() user: User): Promise { + // ensure a valid email value and password value + validateCredentials(_.pick(user, ['email', 'password'])); + + // encrypt the password + user.password = await this.passwordHasher.hashPassword(user.password); + + // create the new user + const savedUser = await this.userRepository.create(user); + delete savedUser.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. + +### Issuing a JWT Token on Successful Login + +In the `UserController` class in the +`loopback4-example-shopping/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. + +```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. + +### Summary + +We've gone through the steps that were used to add JWT `authentication` to the +`loopback4-example-shopping` application. + +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 { + TokenServiceBindings, + UserServiceBindings, + TokenServiceConstants, +} from './keys'; +import {JWTService} from './services/jwt-service'; +import {MyUserService} from './services/user-service'; + +import * as 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'; + +/** + * 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.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')); + + 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); + + 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); + + // // Bind bcrypt hash services + this.bind(PasswordHasherBindings.ROUNDS).to(10); + this.bind(PasswordHasherBindings.PASSWORD_HASHER).toClass(BcryptHasher); + + this.bind(UserServiceBindings.USER_SERVICE).toClass(MyUserService); + } +} +``` + +## Running the Completed Application + +To run the completed application, follow the instructions in the +[Try it out](#try-it-out) section. + +For more information, please visit +[Authentication Component](../../Loopback-component-authentication.md). + +## Bugs/Feedback + +Open an issue in +[loopback4-example-shopping](https://github.com/strongloop/loopback4-example-shopping) +and we'll take a look! + +## Contributions + +- [Guidelines](https://github.com/strongloop/loopback-next/blob/master/docs/CONTRIBUTING.md) +- [Join the team](https://github.com/strongloop/loopback-next/issues/110) + +## Tests + +Run `npm test` from the root folder. + +## Contributors + +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). + +## License + +MIT