Aplicación backend desarrollada con el framework Loopback 4 para el login de usuarios utilizando la estrategia de autenticación JSON Web Token.
-@2x.png)
Instalar las dependencias desde lb4
$ lb4 app- Project name: login
- Project description: login
- App class name: LoginApplication
- Select features to enable in the project: () Enable docker
Crear el modelo con los atributos que tendrá el usuario.
$ lb4 model-
Model class name: user
-
Enter the property name: id
-
Property type: string
-
Is id the ID property? Yes
-
Is it required?: No
-
Enter the property name: email
-
Property type: string
-
Is it required?: Yes
-
Enter the property name: password
-
Property type: string
-
Is it required?: Yes
-
Enter the property name: username
-
Property type: string
-
Is it required?: Yes
-
Enter the property name: firstName
-
Property type: string
-
Is it required?: Yes
-
Enter the property name: lastName
-
Property type: string
-
Is it required?: Yes
Crear la fuente de la base de datos de MongoDB
$ lb4 datasource- Datasource name: db
- Select the connector for db: MongoDB
- host: localhost
- port: 27017
- user:
- password:
- database: users
- Feature supported by MongoDB v3.1.0 and above: Yes
Crear el repositorio del modelo user utilizando la base de datos que se ha creado en el paso anterior
$ lb4 repository- Please select the datasource DbDatasource
- Select the model(s) you want to generate a repository User
- Please select the repository base class DefaultCrudRepository (Legacy juggler bridge)
Crear el controlador para la base de las operaciones CRUD
$ lb4 controller- Controller class name: user
- What kind of controller would you like to generate? REST Controller with CRUD functions
- What is the name of your CRUD repository? UserRepository
- What is the name of ID property? id
- What is the type of your ID? string
- What is the base HTTP path name of the CRUD operations? /users
$ npm i --s @loopback/authenticationEn el archivo src/application.ts
Importamos el componente Authentication:
import { AuthenticationComponent } from '@loopback/authentication';
Y dentro del constructor lo añadimos:
this.component(AuthenticationComponent);
En src/sequence.ts añadimos al constructor:
@inject(AuthenticationBindings.AUTH_ACTION) protected authenticateRequest: AuthenticateFn
Y después llamamos a la acción de authentication y lanzamos error en caso de que existas
async handle(context: RequestContext) {
try {
const { request, response } = context;
const route = this.findRoute(request);
//call authentication action
await this.authenticateRequest(request);
//Authentication successfull, proceed to invoke controller
const args = await this.parseParams(request, route);
const result = await this.invoke(route, args);
this.send(response, result);
} catch (err) {
if (
err.code === AUTHENTICATION_STRATEGY_NOT_FOUND ||
err.code === USER_PROFILE_NOT_FOUND
) {
Object.assign(err, { statusCode: 401 });
}
this.reject(context, err);
return;
}
}
Primero, instalamos el módulo bcryptjs
$ npm i --s bcryptjs
$ npm i --s @types/bcryptjs
Después, instalamos el módulo jsonwebtoken
$ npm i --s jsonwebtoken
Después, copiaremos el archivo keys.ts que se encuentra en /src
Cosas a destacar de este archivo son las constantes:
- TOKEN_SECRET_VALUE: valor que va introducido dentro del token
- TOKEN_EXPIRES: valor en tiempo hasta que el token deje de ser válido
A continuación, en el repositorio user /src/repositories/user.repository.ts añade el siguiente objeto fuera de la clase:
export type Credentials = {
email: string;
password: string;
};
Una vez creadas las keys, necesitaremos un hasher que utilice un algoritmo que convierta el texto plano y lo introduzca como hash en la base de datos. Para ello, crearemos el directorio /src/services y dentro copiaremos el archivo hash.password.bcryptjs.ts que se encuentra en este mismo repositorio.
Ahora pasaremos a definir la estrategia de autenticación, que en este caso hemos elegido JWT. Crearemos la carpeta /src/authentication-strategies/ y dentro de ella copiaremos el archivo /jwt-strategy.ts
Y para acabar, solo nos queda registrar la estrategia de autenticación en el constructor de /src/application.ts:
registerAuthenticationStrategy(this, JWTAuthenticationStrategy);
Creamos el servicio JWTService para poder enviar el token. Dentro de src/services copiamos el archivo jwt-service.ts
Después, creamos el método setUpBindings() dentro de la clase /src/application.ts/
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);
}
E iniciaremos el método dentro del constructor
this.setUpBindings();
A continuación, crearemos el UserService, para ello dentro de la carpeta /src/services copiaremos el archivo user-service.ts y después añadiremos las siguientes líneas de código al método setUpBindings de aplication.ts
this.bind(PasswordHasherBindings.ROUNDS).to(10);
this.bind(PasswordHasherBindings.PASSWORD_HASHER).toClass(BcryptHasher);
this.bind(UserServiceBindings.USER_SERVICE).toClass(MyUserService);
En el archivo /src/controllers/user.controller.ts editaremos la petición POST para añadir usuarios, pero antes añadiremos al constructor:
@inject(PasswordHasherBindings.PASSWORD_HASHER)
public passwordHasher: PasswordHasher,
@inject(TokenServiceBindings.TOKEN_SERVICE)
public jwtService: TokenService,
@inject(UserServiceBindings.USER_SERVICE)
public userService: UserService<User, Credentials>
Y después sustituiremos la petición @post/users por esta:
@post('/users')
async create(@requestBody() user: User): Promise<User> {
// 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;
}
Creamos la carpeta /src/controllers/specs/ y dentro copiamos el archivo user-controller.specs.ts.
Una vez tenemos las constantes declaradas que nos interesan para poder transformar los datos introducidos por el usuario, nos dirigimos al archivo /src/controllers/user.controller.ts y allí introducimos la petición post del login:
@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 };
}
Este controlador servirá para utilizar el decorador @authenticate y que el usuario deba estar en posesión de un token válido para poder acceder al recurso.
A continuación, vamos a asegurar un endpoint GET para que cuando el usuario acceda a /users/me y cuando el servidor compruebe que está autenticado se ejecutará printCurrentUser().
Añadiremos el siguiente código a /src/controlers/user.controller.ts
@get('/users/me', {
responses: {
'200': {
description: 'The current user profile',
content: {
'application/json': {
},
},
},
},
})
@authenticate('jwt')
async printCurrentUser(
@inject(AuthenticationBindings.CURRENT_USER)
currentUserProfile: UserProfile,
): Promise<UserProfile> {
return currentUserProfile;
}