Authentication
The Goal
Set up authorization to the application using passport.
Create login functionality in our application.
The Demo
This feature uses the Users API to create and authenticate users.
Start the service with npm run s:users-api
.
Via curl, you can create a user like so:
curl -X 'POST' \
'http://localhost:3400/api/users' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
"firstName": "test",
"lastName": "test last",
"email": "test@test.com",
"password": "test",
"role": "tester",
"company_id": "test"
}'
Once the user is created, you can log in like so:
curl -X POST http://localhost:3400/api/users/login -d '{"email": "test@test.com", "password": "test"}' -H "Content-Type: application/json"
You will receive a token in the response. You can use this token to authenticate requests to the API.
The Explanation
What is the difference between "local strategy" and "JWT strategy" in Nest?
In NestJS, the term "strategy" is used in the context of authentication and refers to different methods of validating user credentials and handling user sessions.
Local Strategy: This strategy is used for traditional username and password authentication. When a user attempts to log in, the local strategy validates the provided username and password against the stored credentials (usually in a database). If the credentials are valid, the user is authenticated. This strategy doesn't involve tokens or external authentication providers. It's called "local" because the authentication process is handled locally within the application.
JWT Strategy: This strategy is used for token-based authentication using JSON Web Tokens (JWTs). Instead of validating the username and password at each request, the application validates a token. When a user logs in, the server generates a JWT that is then sent back to the client. The client includes this token in the header of subsequent requests. The JWT strategy validates the token and, if valid, considers the user authenticated. This strategy is stateless (the server doesn't need to keep a record of past tokens) and is often used in API authentication.
In summary, the local strategy is used for initial authentication using username and password, while the JWT strategy is used for maintaining the user session through tokens.
What are some commonly used authentication strategies in Nest?
NestJS, being a flexible and modular framework, supports a variety of authentication strategies. Here are some commonly used ones:
Local Strategy: This strategy is used for traditional username and password authentication. The server validates the provided credentials against the stored ones (usually in a database). If the credentials are valid, the user is authenticated.
JWT Strategy: This strategy is used for token-based authentication using JSON Web Tokens (JWTs). The server generates a JWT when a user logs in and sends it back to the client. The client includes this token in the header of subsequent requests. The server validates the token and, if valid, considers the user authenticated.
OAuth2 Strategy: This strategy is used for OAuth2 authentication, a popular method for authenticating users via third-party providers like Google, Facebook, or GitHub. The user is redirected to the third-party provider, and upon successful authentication, is redirected back to the application with an access token.
OpenID Connect Strategy: This strategy is used for OpenID Connect authentication, a layer on top of the OAuth2 protocol. It allows clients to verify the identity of the end-user based on the authentication performed by an authorization server.
SAML Strategy: This strategy is used for SAML-based Single Sign-On (SSO). SAML (Security Assertion Markup Language) is an open standard that allows identity providers to pass authorization credentials to service providers.
Bearer Strategy: This strategy is used for API authentication. It's a simple strategy that checks for a token in the Authorization header of the request.
Remember, the choice of strategy depends on the specific needs of your application.
What authentication options are available when using Nest?
Local Strategy: This is the most basic form of authentication where a username (or email) and password are used for authentication. It's implemented by the passport-local module.
OAuth 2.0 Strategy: OAuth 2.0 is a protocol that allows a user to grant limited access to their resources on one site, to another site, without having to expose their credentials. It's implemented by the passport-oauth2 module.
JWT Strategy: JSON Web Tokens (JWTs) are a compact, URL-safe means of representing claims to be transferred between two parties. The passport-jwt module implements this strategy.
Google OAuth Strategy: This strategy allows users to authenticate using their Google account. It's implemented by the passport-google-oauth20 module.
Facebook Strategy: This strategy allows users to authenticate using their Facebook account. It's implemented by the passport-facebook module.
Twitter Strategy: This strategy allows users to authenticate using their Twitter account. It's implemented by the passport-twitter module.
GitHub Strategy: This strategy allows users to authenticate using their GitHub account. It's implemented by the passport-github module.
The Code
Step 01: Install the Dependencies
npm install --save @nestjs/passport passport passport-local
npm install --save-dev @types/passport-local
npm install --save @nestjs/jwt passport-jwt
npm install --save-dev @types/passport-jwt
npm install --save bcrypt
@nestjs/passport: This is a NestJS module that provides a wrapper around Passport, a popular authentication middleware for Node.js. It simplifies the process of integrating Passport into a NestJS application.
passport: This is the core Passport module. Passport is a flexible and modular authentication middleware for Node.js that can be used to authenticate requests in a variety of ways, including username and password, OAuth, OpenID, etc.
passport-local: This is a Passport strategy for authenticating with a username and password. This module lets you authenticate using a username and password in your Node.js applications.
@types/passport-local: This package contains TypeScript definitions for passport-local. It's a development dependency, meaning it's only needed during development and not when the application is running in production.
@nestjs/jwt: This is a NestJS module that provides functionality for working with JSON Web Tokens (JWTs). JWTs are a compact, URL-safe means of representing claims to be transferred between two parties.
passport-jwt: This is a Passport strategy for authenticating with a JSON Web Token. This allows you to authenticate endpoints using a JWT.
@types/passport-jwt: This package contains TypeScript definitions for passport-jwt. Like @types/passport-local, it's a development dependency.
bcrypt: This is a library to help you hash passwords. Bcrypt is a password-hashing function designed by Niels Provos and David Mazières, based on the Blowfish cipher, and presented at USENIX in 1999. The bcrypt function is the default password hash algorithm for BSD and other systems. The prefix "$2a$" or "2y" in a hash string in a shadow password file indicates that hash string is a bcrypt hash in modular crypt format.
Step 02: Generate the Auth Module
npx nx g @nx/nest:module --name=auth --project=users-api
This command will generate a new module named 'auth' in the 'users-api' project. The module will be a new file that exports a class decorated with @Module(). This class can be used to organize related controllers, providers, and imports.
// apps/auth/auth.constants.ts
export const jwtConstants = {
secret: 'secretKey',
};
This JavaScript code exports a constant object named jwtConstants. This object contains a single property secret with the value 'secretKey'.
The secret is a key that is used by the JSON Web Token (JWT) package to sign the tokens. This means that whenever a token is created, this secret key is used to encrypt the data and produce a unique token. When the token is received back from the client, the same secret key is used to verify the token and decrypt the data.
In this case, the secret key is set to 'secretKey', which is not a good practice for a production application. The secret key should be a long, complex, and unguessable string, and it should be kept secure and secret. It's often stored in environment variables or secure key management systems.
The jwtConstants object is exported so that it can be imported and used in other parts of the application where the JWT package is used.
// apps/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { UsersModule } from '../users/users.module';
import { jwtConstants } from './auth.constants';
import { AuthService } from './auth.service';
import { JwtStrategy } from './jwt.strategy';
import { LocalStrategy } from './local.strategy';
@Module({
imports: [
PassportModule.register({ defaultStrategy: 'jwt' }),
JwtModule.register({
secret: jwtConstants.secret,
signOptions: { expiresIn: '300s' },
}),
UsersModule,
],
providers: [AuthService, LocalStrategy, JwtStrategy],
exports: [AuthService, LocalStrategy, JwtStrategy],
})
export class AuthModule {}
This JavaScript code exports a constant object named jwtConstants. This object contains a single property secret with the value 'secretKey'.
The secret is a key that is used by the JSON Web Token (JWT) package to sign the tokens. This means that whenever a token is created, this secret key is used to encrypt the data and produce a unique token. When the token is received back from the client, the same secret key is used to verify the token and decrypt the data.
In this case, the secret key is set to 'secretKey', which is not a good practice for a production application. The secret key should be a long, complex, and unguessable string, and it should be kept secure and secret. It's often stored in environment variables or secure key management systems.
The jwtConstants object is exported so that it can be imported and used in other parts of the application where the JWT package is used.
This JavaScript code defines a NestJS module named AuthModule. A module is a fundamental organizational unit in a NestJS application. It's a class decorated with @Module().
The @Module() decorator takes an object with several properties that describe the module:
imports: This property is an array of other modules that this module depends on. The AuthModule imports three modules:
PassportModule.register({ defaultStrategy: 'jwt' }): This is the Passport module, configured to use JWT (JSON Web Tokens) as the default authentication strategy.
JwtModule.register({ secret: jwtConstants.secret, signOptions: { expiresIn: '300s' } }): This is the JWT module, configured with a secret key for signing the tokens and an expiration time for the tokens. The secret key is retrieved from jwtConstants.secret.
UsersModule: This is another module that the AuthModule depends on. It's not clear from this code what the UsersModule does, but based on the name, it likely deals with user management. providers: This property is an array of providers that this module will make available to the rest of the application. Providers can be services, repositories, factories, etc. The AuthModule provides three services: AuthService, LocalStrategy, and JwtStrategy.
exports: This property is an array of providers that should be exported from this module and made available to other modules. The AuthModule exports the same three services that it provides: AuthService, LocalStrategy, and JwtStrategy.
The AuthModule class itself doesn't define any methods or properties. Its purpose is to organize related functionality (in this case, authentication functionality) into a module, and to specify the dependencies, providers, and exports of that module.
// apps/auth/auth.service.ts
import { Injectable } from '@nestjs/common';
import { UsersService } from '../users/users.service';
import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcrypt';
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService
) {}
async validateUser(email: string, pass: string): Promise<any> {
const user = await this.usersService.findByEmail(email);
const match = await bcrypt.compare(pass, user.password);
if (match) {
// strip the password property from the user object
const { password, ...result } = user;
return result;
}
return null;
}
async login({ email }: { email: string }) {
const user = await this.usersService.findByEmail(email);
const payload = { email, id: user.id };
const access_token = this.jwtService.sign(payload);
return {
user,
access_token,
};
}
}
This JavaScript code defines an AuthService class for a NestJS application. The class is decorated with @Injectable(), meaning it can be injected as a dependency in other parts of the application.
The AuthService class has two dependencies: UsersService and JwtService. These are injected through the constructor. UsersService is likely used to interact with user data, while JwtService is used to work with JSON Web Tokens (JWTs).
The validateUser method is used to validate a user's credentials. It takes an email and a password as arguments. It uses the UsersService to find a user with the given email. Then, it uses the bcrypt.compare function to compare the given password with the stored password. If the passwords match, it returns the user object without the password property. If they don't match, it returns null.
The login method is used to log a user in. It takes an object with an email property as an argument. It uses the UsersService to find a user with the given email. Then, it creates a payload with the user's email and id, and uses the JwtService to sign this payload and create a JWT. It returns an object with the user object and the JWT.
The bcrypt module is used for hashing and comparing passwords. It's a secure way to store and verify passwords. The @nestjs/jwt module is used for working with JWTs, which are a common method for handling authentication in web applications.
// apps/auth/jwt-auth.guard.ts
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
This JavaScript code defines a JwtAuthGuard class for a NestJS application. The class extends AuthGuard, a built-in guard in NestJS that is part of the @nestjs/passport module.
The AuthGuard class is a part of Passport's integration with NestJS. It provides a way to protect routes and handle authentication logic. By extending AuthGuard, the JwtAuthGuard class inherits this functionality.
The AuthGuard takes a strategy name as an argument. In this case, the strategy is 'jwt', which means this guard will use the JWT (JSON Web Token) strategy for authentication. This strategy is typically used for API authentication where each request must include a signed token.
The @Injectable() decorator is a class decorator provided by NestJS. It marks the class as a provider that can be managed by the NestJS dependency injection system. This means that JwtAuthGuard can be injected as a dependency in other parts of the application.
In summary, JwtAuthGuard is a custom guard that uses the JWT strategy for authentication. It can be used to protect routes and ensure that only authenticated requests are allowed.
// apps/auth/jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt } from 'passport-jwt';
import { Strategy } from 'passport-local';
import { jwtConstants } from './auth.constants';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: jwtConstants.secret,
});
}
async validate(payload: any) {
return { id: payload.sub, username: payload.username };
}
}
This JavaScript code defines a JwtStrategy class for a NestJS application. The class extends PassportStrategy, which is a part of the @nestjs/passport module, a module that integrates Passport.js into NestJS.
In the constructor of JwtStrategy, the super method is called with an options object. This object configures the JWT strategy:
- jwtFromRequest: This is a function that defines how the JWT should be extracted from the request. ExtractJwt.fromAuthHeaderAsBearerToken() is a method provided by passport-jwt that extracts the JWT from the Authorization header with the Bearer schema.
- ignoreExpiration: This is a boolean that defines whether the JWT expiration should be ignored or not. If it's false, expired tokens are rejected.
- secretOrKey: This is the secret key used to sign the tokens. It's retrieved from jwtConstants.secret.
The validate method is a required method when implementing a Passport strategy. This method receives the payload of the token (the data that was signed) when the token is verified. In this case, it returns an object with the user's id and username. This returned object will be included in the Request object of routes that are protected by this guard.
TODO The PassportStrategy takes a strategy as an argument, in this case, Strategy from passport-local. However, this seems to be a mistake as the class name is JwtStrategy, it should be using Strategy from passport-jwt instead of passport-local.
// apps/auth/local-auth.guard.ts
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}
The LocalAuthGuard class is decorated with @Injectable(), a decorator provided by NestJS. This means that instances of this class can be automatically created and injected into other parts of the application by the NestJS dependency injection system.
The class extends AuthGuard('local'), which means it inherits from a class returned by the AuthGuard function, with the string 'local' passed as an argument. This is how the local strategy of Passport is integrated into the NestJS application. The 'local' string refers to the local strategy, which is used for username and password authentication.
The LocalAuthGuard class doesn't define any methods or properties. It simply extends AuthGuard('local') and is decorated with @Injectable(). This means that it inherits all the functionality of AuthGuard('local') and doesn't add any additional functionality. The purpose of this class is to provide a convenient way to use the local auth guard in other parts of the application. Instead of having to use AuthGuard('local') directly, you can use LocalAuthGuard, which is more descriptive and easier to understand.
// apps/auth/local.strategy.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';
import { AuthService } from './auth.service';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({ usernameField: 'email' });
}
async validate(email: string, password: string): Promise<any> {
const user = await this.authService.validateUser(email, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
This JavaScript code is a part of an authentication system for a NestJS application. It uses Passport, a popular authentication middleware for Node.js, and specifically the local strategy of Passport, which is used for username (in this case, email) and password authentication.
The LocalStrategy class is decorated with @Injectable(), a decorator provided by NestJS. This means that instances of this class can be automatically created and injected into other parts of the application by the NestJS dependency injection system.
The class extends PassportStrategy(Strategy), which means it inherits from a class returned by the PassportStrategy function, with the Strategy from passport-local passed as an argument. This is how the local strategy of Passport is integrated into the NestJS application.
In the constructor of LocalStrategy, the AuthService is injected. This service is likely responsible for the actual validation of users. The super call in the constructor is used to call the constructor of the parent class. The object { usernameField: 'email' } passed to super configures the local strategy to use the 'email' field as the username field.
The validate method is an asynchronous method that takes an email and a password as arguments. This method is required by Passport for the local strategy. It's used to validate the credentials provided by the user. The method uses the validateUser method of the AuthService to validate the user. If the user is not valid (i.e., validateUser returns a falsy value), an UnauthorizedException is thrown. If the user is valid, the user object is returned. This user object will then be attached to the request object by Passport, and can be accessed in subsequent middleware and route handlers.
Challenges
$ # POST to /api/users/auth/login
$ curl -X 'POST' \
'http://localhost:3400/api/users/auth/login' \
-H 'accept: */*' \
-d '{"email": "SOME_EMAIL", "password": "SOME_PASSWORD"}'