I'm trying to create a user token based on the secret of the user trying to log in. However instead of using a secret from the environment I want to use a secret assigned to a user object inside the database.
import { Injectable } from '#nestjs/common';
import { JwtService } from '#nestjs/jwt';
import { UserService } from '#src/modules/user/services';
#Injectable()
export class AuthService {
public constructor(private readonly jwtService: JwtService,
private readonly userService: UserService) {}
public async createToken(email: string): Promise<JwtReply> {
const expiresIn = 60 * 60 * 24;
const user = await this.userService.user({ where: { email } });
const accessToken = await this.jwtService.signAsync({ email: user.email },
/* user.secret ,*/
{ expiresIn });
return {
accessToken,
expiresIn,
};
}
}
I'm new to Nestjs and maybe I'm missing something.
node-jsonwebtoken does provide the necessary parameter in the sign(...) function. nestjs/jwt is missing this parameter (see code). How would you solve it without using node-jsonwebtoken or maybe a more abstract question: does my way of handling user secret make sense here? Thanks.
This is not yet possible solely with nest's JwtModule but you can easily implement the missing parts yourself.
Live Demo
You can create tokens by calling the following routes:
user1 (secret: '123'): https://yw7wz99zv1.sse.codesandbox.io/login/1
user2 (secret: '456'): https://yw7wz99zv1.sse.codesandbox.io/login/2
Then call the protected route '/' with your token and receive your user:
curl -X GET https://yw7wz99zv1.sse.codesandbox.io/ \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxIiwiaWF0IjoxNTUzNjQwMjc5fQ.E5o3djesqWVHNGe-Hi3KODp0aTiQU9X_H3Murht1R5U'
How does it work?
In the AuthService I'm just using the standard jsonwebtoken library to create the token. You can then call createToken from your login route:
import * as jwt from 'jsonwebtoken';
export class AuthService {
constructor(private readonly userService: UserService) {}
createToken(userId: string) {
const user = this.userService.getUser(userId);
return jwt.sign({ userId: user.userId }, user.secret, { expiresIn: 3600 });
}
// ...
}
In the JwtStrategy you use secretOrKeyProvider instead of secretOrKey which can asynchronously access the UserService to get the user secret dynamically:
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
private readonly authService: AuthService,
private readonly userService: UserService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKeyProvider: (request, jwtToken, done) => {
const decodedToken: any = jwt.decode(jwtToken);
const user = this.userService.getUser(decodedToken.userId);
done(null, user.secret);
},
});
}
// ...
}
Note that the options you pass to the JwtModule like expiresIn will not be used, instead directly pass your options in the AuthService. Import the JwtModule without any options:
JwtModule.register({})
General
Does my way of handling user secret make sense here?
This is hard to answer without knowing your exact requirements. I guess there are use cases for jwt with dynamic secrets but with it you are losing a great property of jwt: they are stateless. This means that your AuthService can issue a jwt token and some ProductService that requires authentication can just trust the jwt (it knows the secret) without making any calls to other services (i.e. UserService which has to query the database).
If user-related keys are not a hard requirement consider rotating the keys frequently instead by making use of jwt's kid property.
The option to add secret into JwtSignOptions has been added in nestjs/jwt version 7.1.0.
With that, the example would be:
public async createToken(email: string): Promise<JwtReply> {
const expiresIn = 60 * 60 * 24;
const user = await this.userService.user({ where: { email } });
const accessToken = await this.jwtService.signAsync(
{ email: user.email },
{ expiresIn,
secret: user.secret,
});
return {
accessToken,
expiresIn,
};
}
I had also case to sign access and refresh tokens with different secret keys.
If u follow nestjs docs, u see JwtModule is registered with single config and token is signed without options (with default config). To use jwtService sign function with options import JwtModule.register with empty object
import { JwtModule } from '#nestjs/jwt';
#Module({
imports: [JwtModule.register({})],
providers: [],
controllers: []
})
export class AuthModule {}
And making config file with different sign options
#Injectable()
export class ApiConfigService {
constructor(private configService: ConfigService) {
}
get accessTokenConfig(): any {
return {
secret: this.configService.get('JWT_ACCESS_TOKEN_KEY'),
expiresIn: eval(this.configService.get('JWT_ACCESS_TOKEN_LIFETIME'))
}
}
get refreshTokenConfig(): any {
return {
secret: this.configService.get('JWT_REFRESH_TOKEN_KEY'),
expiresIn: eval(this.configService.get('JWT_REFRESH_TOKEN_LIFETIME'))
}
}
}
u may sign token with desired config
#Injectable()
export class AuthService {
constructor(private jwtService: JwtService, private apiConfigService: ApiConfigService ) {}
login(user: any) {
let payload = {username: user.username, id: user.id};
let jwt = this.jwtService.sign(payload, this.apiConfigService.accessTokenConfig);
//
return { token: jwt };
}
}
Related
I have the below two guards in NestJS(one for api key based authentication and another for token based authentication).The ApiKeyGuard is the top priority.I want to implement a system where if anyone has a key it will not check the other guard.Is there any way I can make the AuthGuard optional based on whether the first Guard passed in cases where there is a ApiKeyGuard?
// Can be accessed with token within app as well as third party users
#UseGuards(ApiKeyGuard, AuthGuard)
#Get('/get-products')
async getProducts(): Promise<any> {
try {
return this.moduleRef
.get(`appService`, { strict: false })
.getProducts();
} catch (error) {
throw new InternalServerErrorException(error.message, error.status);
}
}
// Only to be accessed with token within app
#UseGuards(AuthGuard)
#Get('/get-users')
async getUsers(): Promise<any> {
try {
return this.moduleRef
.get(`appService`, { strict: false })
.getUsers();
} catch (error) {
throw new InternalServerErrorException(error.message, error.status);
}
}
The below guard is used to check for api key based authentication
api-key.guard.ts
#Injectable()
export class ApiKeyGuard implements CanActivate {
constructor(private readonly apiKeyService: ApiKeyService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest();
const key = req.headers['X-API-KEY'] ?? req.query.api_key;
return this.apiKeyService.isKeyValid(key);
}
The below guard is used to check for token based authentication
authentication.guard.ts
#Injectable()
export class AuthGuard implements CanActivate, OnModuleInit {
constructor(private readonly moduleRef: ModuleRef) {}
onModuleInit() {}
async canActivate(context: ExecutionContext): Promise<boolean> {
try {
// Get request data and validate token
const request = context.switchToHttp().getRequest();
if (request.headers.authorization) {
const token = request.headers.authorization.split(' ')[1];
const response = await this.checkToken(token);
if (response) {
return response;
} else {
throw new UnauthorizedException();
}
} else {
throw new UnauthorizedException();
}
} catch (error) {
throw new UnauthorizedException();
}
}
}
What I did was using #nestjs/passport and using the AuthGuard and making custom PassportJS strategies. I had a similar issue, and looked for a way to accomplish this without using some "magic". The documentation can be found here.
In the AuthGuard, you can add multiple guards. It's a bit hidden away in the documentation, although it is very powerful. Take a look here, especially the last line of the section, it states:
In addition to extending the default error handling and authentication logic, we can allow authentication to go through a chain of strategies. The first strategy to succeed, redirect, or error will halt the chain. Authentication failures will proceed through each strategy in series, ultimately failing if all strategies fail.
Which can be done like so:
export class JwtAuthGuard extends AuthGuard(['strategy_jwt_1', 'strategy_jwt_2', '...']) { ... }
Now, back to your example, you've to create 2 custom strategies, one for the API key and one for the authorization header, and both these guards should be activated.
So for the API strategy (as example):
import { Strategy } from 'passport-custom';
import { Injectable } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import { Strategy } from 'passport-custom';
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
#Injectable()
export class ApiStrategy extends PassportStrategy(Strategy, 'api-strategy') {
constructor(private readonly apiKeyService: ApiKeyService) {}
async validate(req: Request): Promise<User> {
const key = req.headers['X-API-KEY'] ?? req.query.api_key;
if ((await this.apiKeyService.isKeyValid(key)) === false) {
throw new UnauthorizedException();
}
return this.getUser();
}
}
Do something similar for your other way of authenticating, and then use the Passport guard as follows:
#UseGuard(AuthGuard(['api-strategy', 'other-strategy'])
This way, the guard will try all strategies (in order) and when all of them fail, your authentication has failed. If one of them succeeds, you're authenticated!
I'am developing a Nestjs micro-service and need to parse the incoming JWT in the request to the outgoing http requests, but seems like there is no cleaner way to do that, or am I missing something?
This is what I have done so far
Note: Using nestjs axios http client
user.contoller.ts
#Controller('/users')
export class UserController {
constructor(private userService: userService,
private vehicleService: VehicleService){}
#Post()
create(#Req() req: Request, #Body() userData: userData) {
// I can get the JWT token from here
// fetch some data from different micro-service
const d = this.vehicleService.getVehicleById(userData.vehicleId, req.headers['authorization'])
// save user
}
}
vehicle.service.ts
#Injectable()
export class VehicleService {
basePath = http://ms-vehicles/api/v1/vehicles;
constructor(private httpService: HttpService) {}
getVehicleById(vehicleId: string, token: string) {
return this.httpSerice.get(this.basePath + '/' + vehicleId,
{headers: {Authorization: token});
}
}
We can intercept the outgoing http calls using axios interceptor, but How can I set the correct token from there, without parsing the token through the the controller and service as a parameter
Found below solution in the stack overflow and I'am not sure this is the right way to do this.
interceptor
#Injectable()
export class HttpServiceInterceptor implements NestInterceptor {
constructor(private httpService: HttpService) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
// ** if you use normal HTTP module **
const ctx = context.switchToHttp();
const token = ctx.getRequest().headers['authorization'];
// ** if you use GraphQL module **
const ctx = GqlExecutionContext.create(context);
const token = ctx.getContext().token;
if (ctx.token) {
this.httpService.axiosRef.defaults.headers.common['authorization'] =
token;
}
return next.handle().pipe();
}
}
Register interceptor in main module as global one
#Module({
providers: [
{
provide: APP_INTERCEPTOR,
useClass: HttpServiceInterceptor,
},
],
})
export class AppModule {}
if I use above solution, can we guarantee that someone else's token will not be used in other requests, because this common headers available for any places where we used httpService? or am I worong?
I create an authentication middleware in NestJs like below:
#Injectable()
export class AuthenticationMiddleware implements NestMiddleware {
constructor() {}
async use(req: any, res: any, next: () => void) {
const authHeaders = req.headers.authorization;
if (authHeaders) {
//some logic etc.
//req.user = user;
next();
} else {
throw new UnathorizedException();
}
}
}
... where I get from headers - an auth token, decode it and check if this user is correct and exists in database, if he exists then i set user object into req.user. And now I have a question, how to get this req.user in my services and use in business logic? I need to get id from req.user but I do not know how.
I know that I can do this by using #Req() request in controller parameters and pass this request into my function, but I do not want it, cause is (for me) a ugly practice. So, how to get this req.user into my services?
thanks for any help!
Well, to get the user in the service you have two options:
use #Req() in the controller and pass it, as you have mentioned
Make your service REQUEST scoped and inject the request object into the service
Personally, I'd go with the former, as request scoping has its own pros and cons to start weighing and dealing with (like not being able to use the service in a passport strategy or a cron job). You can also just make the user optional, or bundle it into the body or whatever is passed to the service and then have access to it without it being an explicit parameter.
You can create a decorator to do it. Something like this
current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const CurrentUser = createParamDecorator(
(property: string, ectx: ExecutionContext) => {
const ctx = ectx.getArgByIndex(1);
return property ? ctx.req.user && ctx.req.user[property] : ctx.req.user;
},
);
example.controller.ts
#ApiTags('example')
#Controller('example')
export class ExampleController {
constructor(private readonly exampleService: ExampleService) {}
#Get('/')
public async doSomething(#CurrentUser() user: YourUserClassOrInteface,): Promise<any> {
return this.exampleService.exampleFunction(user.id);
}
}
example.service.ts
export class ExampleService {
constructor() {}
public async exampleFunction(id: string): Promise<void> {
console.log('id:', id);
return;
}
}
IMPORTANT: Injecting the Request in the services is not a good solution because it will make a new one in each endpoint request. That is why the Decorators are used. It will make it easy to work with needed data and do not hand over only the parameters that are needed instead of transferring the extra big request object.
Alternative solution(if you won't use request scoped injection): you can use async hooks. There is many libraries which simplify async hooks usage, like this one. You simply set your context in middleware:
#Injectable()
export class AuthenticationMiddleware implements NestMiddleware {
constructor() {}
async use(req: any, res: any, next: () => void) {
const authHeaders = req.headers.authorization;
if (authHeaders) {
//some logic etc.
//req.user = user;
Context.run(next, { user: req.user });
} else {
throw new UnathorizedException();
}
}
}
And then you can get user instance in any place in your code by simply calling Context.get()
You can define your own Request interface like this
import { Request } from 'express';
...
export interface IRequestWithUser extends Request {
user: User;
}
then just give the type of req parameter to IRequestWithUser.
I am implementing linkedin login strategy with passport in nestJS.
What I have right now is that I have a button "Login with linkedin" points to auth/linkedin.
#Get('auth/linkedin')
#UseGuards(AuthGuard('linkedin'))
async linkedinAuth(#Req() req) {
}
Which works great and takes me to the linkedin login page and hits back my callback URL which is auth/linkedin/callback with code query string of token and this is where I am unable to figure out what todo and how to return linkedin user
#Get('auth/linkedin/callback')
#UseGuards(AuthGuard('linkedin'))
linkedinCallBack(#Req() req) {
console.log(req)
return 'handle callback!';
}
Linkedin passport strategy :
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
const LinkedInAuthStrategy = require('passport-linkedin-oauth2').Strategy;
#Injectable()
export class LinkedinStrategy extends PassportStrategy(LinkedInAuthStrategy) {
constructor(
) {
super({
clientID: 'abcd123',
clientSecret: 'abcd123',
callbackURL: 'http://localhost:5000/auth/linkedin/callback',
scope: ['r_emailaddress', 'r_liteprofile'],
}, function(accessToken, refreshToken, profile, done) {
process.nextTick(function () {
console.log(profile);
return done(null, profile);
});
})
}
}
Note: I am using this package for linkedin passport strategy
Question : How can I handle callback further with #UseGuards, and return LinkedIn user?
You should adapt the LinkedinStrategy class a bit. You can't use the done function directly. It will be called by nest. You should have a validate method and return a user object from it. That object will be set to the request object so in the controller you will be able to access it with req.user. This is approximately how your class should look:
import { Strategy } from 'passport-linkedin-oauth2';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { AuthService } from './auth.service';
#Injectable()
export class LinkedinStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({
clientID: 'abcd123',
clientSecret: 'abcd123',
callbackURL: 'http://localhost:5000/auth/linkedin/callback',
scope: ['r_emailaddress', 'r_liteprofile'],
});
}
async validate(accessToken: string, refreshToken: string, profile: object): Promise<any> {
const user = await this.authService.validateUser(profile);
return user;
}
}
Check this article: OAuth2 in NestJS for Social Login (Google, Facebook, Twitter, etc) and this repo: https://github.com/thisismydesign/nestjs-starter
In the validate method of LinkedinStrategy you need to find or create the user (probably store in your DB), e.g.:
export class LinkedinStrategy extends PassportStrategy(Strategy) {
// constructor...
async validate(accessToken, refreshToken, profile) {
let user = await this.usersService.findOneByProvider('linkedin', profile.id);
if (!user) {
user = await this.usersService.create({
provider: 'linkedin',
providerId: id,
name: profile.name,
username: profile.email,
});
}
return user;
}
}
In the controller's callback endpoint you can issue a JWT token to handle the User's session within the app:
#Get('auth/linkedin/callback')
#UseGuards(AuthGuard('linkedin'))
linkedinCallBack(#Req() req) {
const { accessToken } = this.jwtAuthService.login(req.user);
res.cookie('jwt', accessToken);
return res.redirect('/profile');
}
I can't get user from request in decorator nest, pleas help me.
Middleware good working it find user by token and save user in request
my middleware:
import { Injectable, NestMiddleware, HttpStatus } from '#nestjs/common';
import { HttpException } from '#nestjs/common/exceptions/http.exception';
import { Request, Response } from 'express';
import { AuthenticationService } from '../modules/authentication-v1/authentication.service';
#Injectable()
export class AuthenticationMiddleware implements NestMiddleware {
constructor(
private readonly authenticationService : AuthenticationService
) {
}
async use(req: Request, res: Response, next: Function) {
let token = req.headers;
if(!token) {
throw new HttpException('token is required', 401);
}
if (!token.match(/Bearer\s(\S+)/)) {
throw new HttpException('Unsupported token', 401);
}
const [ tokenType, tokenValue ] = token.split(' ');
try {
const result = await this.authenticationService.getAccessToken(tokenValue);
req.user = result;
next();
} catch (e) {
throw new HttpException(e.message, 401);
}
}
}
but here request don't have property user and i don't know why
user decorator:
export const User = createParamDecorator((data: any, req) => {
return req.user; // but here user undefined
});
app module:
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AuthenticationMiddleware)
.forRoutes({ path: 'auto-reports-v1', method: RequestMethod.GET });
}
}
route method:
#UseInterceptors(LoggingInterceptor)
#Controller('auto-reports-v1')
#ApiTags('auto-reports-v1')
export class AutoReportsController {
constructor(private readonly autoReportsService: AutoReportsService) {}
#Get()
async findAll(
#Query() filter: any,
#User() user: any): Promise<Paginated> {
return this.autoReportsService.findPaginatedByFilter(filter, user);
}
}
In NestJS with Fastify, middleware attaches values to req.raw. This is because middleware runs before the request gets wrapped by the FastifyRequest object, so all value attachments are made to the IncomingRequest object (same as Express Request object). Fastify will then wrap the IncomingRequest in its own FastifyRequest object and expose the IncomingRequest through req.raw meaning the user you are looking for is at req.raw.user not req.user. If you want to have the same functionality across Express and Fastify, I'd suggest using a Guard instead.
Accepted answer didn't help me, but the "custom decorators" from nest js documentation uses createParamDecorator with ExecutionContext as second parameter, and this worked for me:
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);