CRUD todo-list API usando o LoopBack4 com Autenticação

Leandro Gomes
8 min readApr 16, 2021

Usando a CMD vamos criar o nosso projeto ao qual podemos chamar por exemplo app-todo-list-autenticacao, executando o seguinte código na CMD

lb4 app-todo-list-autenticacao

Depois preenchemos os dados como na figura seguinte

depois de estar o projeto criado executamos na CMD o seguinte comando para entrarmos na pasta do projeto

cd app-todo-list-autenticacao

Agora vamos começar a definir as propriedades do nosso modelo, para isso vamos executar o seguinte comando

lb4 model

E depois preencher os dados do nosso modelo como na figura seguinte

Assim deve ficar o nosso ficheiro todo.model.ts que está na pasta models, que por sua vez está na pasta src

import {Entity, model, property} from ‘@loopback/repository’;@model()
export class Todoautenticacao extends Entity {
@property({
type: ‘number’,
id: true,
generated: false,
default: 1,
})
id?: number;
@property({
type: ‘string’,
required: true,
})
title: string;
@property({
type: ‘string’,
})
desc?: string;
@property({
type: ‘boolean’,
})
isComplete?: boolean;
@property({
type: ‘string’,
})
remindAtAddress?: string;
@property({
type: ‘string’,
})
remindAtGeo?: string;
@property({
type: ‘any’,
})
tag?: any;
constructor(data?: Partial<Todoautenticacao>) {
super(data);
}
}
export interface TodoautenticacaoRelations {
// describe navigational properties here
}
export type TodoautenticacaoWithRelations = Todoautenticacao & TodoautenticacaoRelations;

Agora vamos criar a datasource usando o seguinte comando

lb4 datasource

Preenchemos os dados

O nome da datasource e selecionamos a forma como queremos guardar os dados da nossa API, para este exemplo vamos guardar na MongoDB selecionando a seguinte opção na CMD

Preenchemos os dados da seguinte forma

Vamos agora definir o repositório, inserindo o seguinte comando na CMD

lb4 repository

E seguir os seguintes passos e executar, para que seja criado

Vamos agora definir o controller para que seja criado, usando o seguinte comando na CMD

lb4 controller

Passamos agora ao todo.controller.ts, onde temos que alterar a função create, de maneira a obrigar o _id da MongoDB a ser numérico, a função create deverá ficar da seguinte forma

Compor o texto a seguir para o código da imagem anterior

A primeira linha assinalada a vermelho serve para quando estamos a inserir um novo documento ser omitido ou excluído o campo id.

O bloco de código assinalado a vermelho é onde primeiro guardamos na variável todoId o id do ultimo registo inserido, depois se o todoId não for null, passamos o id da variável todoId para uma variável aux, fazemos o parse do todoId.id, depois incrementamos a variável aux e por fim atribuímos o valor da variável aux ao campo id da nossa coleção todoautenticacao.id.

Esta alteração no código serve para forçar o _id da nossa base de dados no MongoDB a ser inteiro e incrementável, em vez de usar o ObjectId que o MongoDB usa por defeito para o _id.

Vamos agora fazer as alterações necessárias, para acrescentarmos a autenticação a nossa API, começamos por instalar as dependências necessárias, executando o seguinte código na CMD

npm i — save @loopback/authentication @loopback/authentication-jwt

Depois vamos ao ficheiro application.ts, que está na pasta src, para ser mais simples substituímos o código que está neste ficheiro pelo seguinte código

import {BootMixin} from ‘@loopback/boot’;
import {ApplicationConfig} from ‘@loopback/core’;
import {
RestExplorerBindings,
RestExplorerComponent,
} from ‘@loopback/rest-explorer’;
import {RepositoryMixin} from ‘@loopback/repository’;
import {RestApplication} from ‘@loopback/rest’;
import {ServiceMixin} from ‘@loopback/service-proxy’;
import path from ‘path’;
import {MySequence} from ‘./sequence’;
// — — — — — ADD IMPORTS autenticacao — — — — — — -
import {AuthenticationComponent} from ‘@loopback/authentication’;
import {
JWTAuthenticationComponent,
SECURITY_SCHEME_SPEC,
UserServiceBindings,
} from ‘@loopback/authentication-jwt’;
import {DbDataSource} from ‘./datasources’;
// — — — — -
export {ApplicationConfig};export class AppTodoListAutenticacaoApplication extends BootMixin(
ServiceMixin(RepositoryMixin(RestApplication)),
) {
constructor(options: ApplicationConfig = {}) {
super(options);
// Set up the custom sequence
this.sequence(MySequence);
// 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,
},
};

// — — — ADD SNIPPET AT THE BOTTOM “autenticacao” — — — — -
// Mount authentication system
this.component(AuthenticationComponent);
// Mount jwt component
this.component(JWTAuthenticationComponent);
// Bind datasource
this.dataSource(DbDataSource, UserServiceBindings.DATASOURCE_NAME);
// — — — — — — — END OF SNIPPET — — — — — — -
}
}
De seguida vamos ao ficheiro sequence.ts que está na pasta src e vamos substituir o código que lá está pelo seguinte códigoimport {MiddlewareSequence} from ‘@loopback/rest’;// — — — — — ADD IMPORTS — — — — — — -
import {
AuthenticateFn,
AuthenticationBindings,
AUTHENTICATION_STRATEGY_NOT_FOUND,
USER_PROFILE_NOT_FOUND,
} from ‘@loopback/authentication’;
export class MySequence extends MiddlewareSequence {

}

Agora vamos criar o controlador para os users, na CMD executamos o seguinte comando

lb4 controller

E vamos defini-lo da seguinte forma

Agora no nosso user.controller.ts, que está na pasta controllers da pasta src, vamos substituir o código pelo seguinte código

// Copyright IBM Corp. 2020. All Rights Reserved.
// Node module: @loopback/example-todo-jwt
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
import {authenticate, TokenService} from ‘@loopback/authentication’;
import {
Credentials,
MyUserService,
TokenServiceBindings,
User,
UserRepository,
UserServiceBindings,
} from ‘@loopback/authentication-jwt’;
import {inject} from ‘@loopback/core’;
import {model, property, repository} from ‘@loopback/repository’;
import {
get,
getModelSchemaRef,
post,
requestBody,
SchemaObject,
} from ‘@loopback/rest’;
import {SecurityBindings, securityId, UserProfile} from ‘@loopback/security’;
import {genSalt, hash} from ‘bcryptjs’;
import _ from ‘lodash’;
@model()
export class NewUserRequest extends User {
@property({
type: ‘string’,
required: true,
})
password: string;
}
const CredentialsSchema: SchemaObject = {
type: ‘object’,
required: [‘email’, ‘password’],
properties: {
email: {
type: ‘string’,
format: ‘email’,
},
password: {
type: ‘string’,
minLength: 8,
},
},
};
export const CredentialsRequestBody = {
description: ‘The input of login function’,
required: true,
content: {
‘application/json’: {schema: CredentialsSchema},
},
};
export class UserController {
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,
) {}
@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};
}
@authenticate(‘jwt’)
@get(‘/whoAmI’, {
responses: {
‘200’: {
description: ‘Return current user’,
content: {
‘application/json’: {
schema: {
type: ‘string’,
},
},
},
},
},
})
async whoAmI(
@inject(SecurityBindings.USER)
currentUserProfile: UserProfile,
): Promise<string> {
return currentUserProfile[securityId];
}
@post(‘/signup’, {
responses: {
‘200’: {
description: ‘User’,
content: {
‘application/json’: {
schema: {
‘x-ts-type’: User,
},
},
},
},
},
})
async signUp(
@requestBody({
content: {
‘application/json’: {
schema: getModelSchemaRef(NewUserRequest, {
title: ‘NewUser’,
}),
},
},
})
newUserRequest: NewUserRequest,
): Promise<User> {
const password = await hash(newUserRequest.password, await genSalt());
const savedUser = await this.userRepository.create(
_.omit(newUserRequest, ‘password’),
);
await this.userRepository.userCredentials(savedUser.id).create({password});return savedUser;
}
}

Vamos agora ao todo.controller.ts que está na pasta controllers da pasta src e vamos depois do ultimo import acrescentar o seguinte código

// — — — — — ADD IMPORTS — — — — — — -
import {authenticate} from ‘@loopback/authentication’;
// — — — — — — — — — — — — — — — — — —
@authenticate(‘jwt’) // ← — Apply the @authenticate decorator at the class level

Agóra vamos colocar a função count acessivél sem ser preciso autenticação, ainda no ficheiro todo.controller.ts que está na pasta controllers da pasta src, antes da função count inserimos o seguinte código

//para nao precisar autenticacao para executar a funcao count
@authenticate.skip()

E a função count ficará assim

Para ativar o servidor e podermos experimentar a nossa API executamos o seguinte comando

npm start

De seguida inserimos o seguinte url no browser

http://localhost:3000/explorer/#/

Este será o resultado, onde temos todas as funções da nossa API

Ao tentarmos inserir um novo documento sem termos Autenticação o resultado será o seguinte

Dá-nos o seguinte erro “UnauthorizedError ”, que é um erro de autenticação, vamos agora executar a função count

Como podemos ver na imagem anterior, conseguimos executar a função count embora nos devolva o valor 0, uma vez que ainda não temos nenhum documento inserido na nossa base de dados, vamos então fazer o signup da seguinte forma

Depois podemos ver no MongoDBCompass que foi criada uma base de dados chamada demoAutenticacao e duas coleções uma, User e outra UserCardentials, onde já temos o user que criamos e que vamos usar para pedir a token que nos vai permitir ter acesso a todas as funções da API, como podemos ver na imagem seguinte

Depois vamos fazer o login para recebermos uma token para termos acesso a todas as funções

vamos então copiar a token que nos foi enviada para termos acesso a todas as funções

Vamos inserir um novo documento

Na linha que está sublinhada encontra-se a token que usamos e mais a baixo esta assinalado o resultado da inserção, vamos agora ver na mongoDBCompass se foi inserido o todo que criamos

Tal como esperado temos mais uma coleção na nossa base de dados a Todoautenticacao com o todo que criamos, que como podemos ver em vez do ObjectId tem um _id e como este foi o primeiro tem o _id 1.

Vamos agora fazer logout e ver que sem nos autenticarmos conseguimos saber quantos todos existem na base de dados.

Assim temos uma todo-list API que guarda os dados no MongoDB e que usa autenticação, para aceder as funções (Premium)principais da API.

Este projeto está disponível no repositório do github.

--

--

Leandro Gomes

Estudante de Engenharia Informática do Instituto Politécnico da Guarda.