how can i decode jwt cookies in a decorator in nestjs? i can't use "private readonly jwtService: JwtService" in decorator, i use jwt-decode but it still work while jwt is out of date
You can create a custom decorator in that case.
//user.decorator.ts
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const User = createParamDecorator((data: any, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
});
Now we can use this User decorator in controllers
//user.controller.ts
import { User } from './user.decorator';
#Get()
async getUser(#User() user) {
//console.log(user);
}
You should have to use AuthGuard to decode the JwtToken
Please refer below document from NestJs
https://docs.nestjs.com/security/authentication#implementing-passport-jwt
Related
I am trying to use JWT in my API, and configuration is completed, can use postman tool to access data from it. However when I use Blazor as front end to access it , the request doesn't have token, so always give a 401 code.
Below is my Blazor code.
program.cs
builder.Services.AddHttpClient<IOptionService, OptionService> ("OptionAPI", (sp, cl) => {
cl.BaseAddress = new Uri("https://localhost:7172");
});
builder.Services.AddScoped(
sp => sp.GetService<IHttpClientFactory>().CreateClient("OptionAPI"));
OptionService.cs
public class OptionService : IOptionService {
private readonly HttpClient _httpClient;
public OptionService(HttpClient httpClient) {
_httpClient = httpClient;
}
public async Task<IEnumerable<OptionOutputDto>> GetOptionsAsync(Guid quizId, Guid questionId) {
_httpClient.DefaultRequestHeaders.Authorization
= new AuthenticationHeaderValue("Bearer", "token");
return await JsonSerializer.DeserializeAsync<IEnumerable<OptionOutputDto>>(
await _httpClient.GetStreamAsync($"api/quizzes/{quizId}/{questionId}/options"),
new JsonSerializerOptions {
PropertyNameCaseInsensitive = true
});
}
I tired use " new AuthenticationHeaderValue("Bearer", "token");" to attach token in header, but its not working, still give 401 code.
And I also tried use
private readonly IHttpClientFactory _httpClient;
public OptionService(IHttpClientFactory httpClient) {
_httpClient = httpClient;
}
public async Task<IEnumerable<OptionOutputDto>> GetOptionsAsync(Guid quizId, Guid questionId) {
var newHttpClient = _httpClient.CreateClient();
newHttpClient.DefaultRequestHeaders.Authorization
= new AuthenticationHeaderValue("Bearer", "token");
return await JsonSerializer.DeserializeAsync<IEnumerable<OptionOutputDto>>(
await newHttpClient.GetStreamAsync($"api/quizzes/{quizId}/{questionId}/options"),
new JsonSerializerOptions {
PropertyNameCaseInsensitive = true
});
}
it's also not working, give me an error,
Unhandled exception rendering component: A suitable constructor for type 'Services.OptionService' could not be located. Ensure the type is concrete and all parameters of a public constructor are either registered as services or passed as arguments. Also ensure no extraneous arguments are provided.
System.InvalidOperationException: A suitable constructor for type .....
Can anyone has a simple way to attach token in request header?
Thanks in advance.
I think the good option is :
builder.Services.AddHttpClient<IOptionService, OptionService> ("OptionAPI", (sp, cl) => {
cl.BaseAddress = new Uri("https://localhost:7172");
});
Could you check if the token is present in header or not?
Your error is most likely related to how the OptionService is being registered in dependency injection. It either needs an empty constructor adding - and/or - you need to ensure that the constructor has all of its dependencies registered correctly in the ServicesCollection too.
The exception is quite explicit:
Ensure the type is concrete and all parameters of a public constructor
are either registered as services or passed as arguments. Also ensure
no extraneous arguments are provided
I gave a similar answer here. Basically you need to include the BaseAddressAuthorizationMessageHandler when defining your httpclients. If you're using a typed httpclient, you can inject the IAccessTokenProvider and get the token from there. Kinda like this:
public class MyHttpClient(IAccessTokenProvider tokenProvider, HttpClient httpClient)
{
_tokenProvider = tokenProvider;
_httpClient = httpClient;
}
private async Task RequestAuthToken()
{
var requestToken = await _tokenProvider.RequestAccessToken();
requestToken.TryGetToken(out var token);
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Value);
}
public async Task<IEnumerable<ReplyDto>> SendHttpRequest()
{
await RequestAuthToken();
return await JsonSerializer.DeserializeAsync<IEnumerable<ReplyDto>>(
await _httpClient.GetStreamAsync("api/getendpoint"),
new JsonSerializerOptions {
PropertyNameCaseInsensitive = true
});
}
I am using Nest.js JWT to protect my resources but i keep getting internal server error when i dont provide token or the token is invalid instead of get unauthorization exception as shown in the following jwt strategy file
import { Injectable, UnauthorizedException } from "#nestjs/common";
import { PassportStrategy } from "#nestjs/passport";
import { ExtractJwt, Strategy } from "passport-jwt";
import { AuthService } from "../services/auth.service";
export interface JwtPayload {
user: string,
refreshToken: boolean
}
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
private authService: AuthService
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.SECURITY_KEY,
});
}
async validate(payload: JwtPayload) {
// prevent passing refresh token as access token
if (payload.refreshToken) {
throw new UnauthorizedException('Access Token Only');
}
const user = await this.authService.getUserFromJwtPayload(payload.user);
if (!user) {
throw new UnauthorizedException('Invalid User');
}
// checks if user logged out
if (!user.refreshToken) {
throw new UnauthorizedException('You have logged out');
}
return user;
}
}
Please assist me i dont know why am keeping getting internal server error, is there any place missing or there is something i have to do.
I think something is going wrong in this process:
const user = await this.authService.getUserFromJwtPayload(payload.user);
So it does not get tot the part where you check if you have a valid user or refreshToken. I think that if you remove both if statements that you will still get the internal server error.
Make sure you run the following command if you get internal server error npm install #nestjs/jwt #nestjs/passport #nestjs/passport-jwt passport passport-jwt
Needs to DI Kafka client in guard:
auth.guard.ts
export class AuthGuard implements CanActivate {
private _client: ClientKafka; <----- // TODO implement nestjs DI mechanism
public async canActivate(context: ExecutionContext): Promise<boolean> {
try {
const request = context.switchToHttp().getRequest();
const authorization: string = request.get('Authorization');
...code here just send data to jwt service...
return true;
} catch (err) {
return false;
}
}
}
I use new in canActivate for creating an instance of Kafka client in auth.guard.ts. But how to inject a class in guard with #Inject? I used to create #Global module, which provides and export Kafka client class, but it's not working...
Use This in the module for globally using the guard
providers: [{provide: APP_GUARD, useClass: AuthGuard}]
As for your question about injecting a class inside a guard, you need to inject it inside the constructor of the AuthGuard class
export class AuthGuard implements CanActivate {
constructor(private clientKafka : ClientKafka){}
}
if this doesn't work, try using
constructor(#Inject(private clientKafka : ClientKafka)){}
Hope this resolves your issue :)
I have a legacy jax-rs request. I can't change it. It's body has OpenID access token. I want to validate it using quarkus-oidc. My idea is to read the body and put token to Authorization header.
I tried to use ContainerRequestFilter with and without quarkus proactive auth, but looks like quarkus auth checks happen way before jax-rs, somewhere in vert.x
I found this Quarkus Custom authorization interceptors, but it works only if access token is in a query string.
How do i read request body and write access token in the headers before quarkus-oidc checks access token?
I fixed! Not sure if this is most correct way to do what i want, but looks like it works reliably.
import io.quarkus.vertx.web.RouteFilter;
import io.vertx.core.http.HttpMethod;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
public class JoinServerRequestSecurityRouterFilter {
#RouteFilter(3000)
public void extractBody(RoutingContext context) {
if (context.request().method() != HttpMethod.POST) {
context.next();
return;
}
if (!"/session-service/join".equals(context.normalizedPath())) {
context.next();
return;
}
BodyHandler bodyHandler = BodyHandler.create(false);
bodyHandler.handle(context);
}
#RouteFilter(3000 - 1)
public void copyAccessToken(RoutingContext context) {
if (context.request().method() != HttpMethod.POST) {
context.next();
return;
}
if (!"/session-service/join".equals(context.normalizedPath())) {
context.next();
return;
}
if (context.getBodyAsJson() == null) {
context.next();
return;
}
String accessToken = context.getBodyAsJson().getString("accessToken");
context.request().headers().add("Authorization", "Bearer " + accessToken);
context.next();
}
}
Sorry for my bad english, I'm from Ukraine :)
Could you tell me how can I create my own service, that extends of Jwt service provided jwt module from npm package? I want to create my own JwtService for catch errors and isolate duplicate logic for token creation and verification. Please, help me how can I do it. Code samples attached.
import { BadRequestException, Injectable } from '#nestjs/common';
import { JwtService as NestJwtService, JwtVerifyOptions } from '#nestjs/jwt';
#Injectable()
export class OwnJwtService extends NestJwtService {
constructor() {
super({});
}
async verifyAsync<T>(token: string, options?: JwtVerifyOptions): Promise<T> {
try {
const res = await super.verifyAsync(token, options);
console.log('res', res);
return res;
} catch (error) {
// My own logic here ...
throw new BadRequestException({
error,
message: 'Error with verify provided token',
});
}
}
}
or maybe I need to inject nestjs jwt service to my own service ? example:
import { BadRequestException, Injectable } from '#nestjs/common';
import { JwtService as NestJwtService, JwtVerifyOptions } from '#nestjs/jwt';
#Injectable()
export class OwnJwtService {
constructor(private readonly jwtService: NestJwtService) {}
async verifyAsync<T>(token: string, options?: JwtVerifyOptions): Promise<T> {
try {
const res = await this.jwtService.verifyAsync(token, options);
console.log('res', res);
return res;
} catch (error) {
throw new BadRequestException({
error,
message: 'Error with verify provided token',
});
}
}
}
and
import { JwtModule as NestJwtModule } from '#nestjs/jwt';
import { ConfigModule, ConfigService } from '#nestjs/config';
import { Module } from '#nestjs/common';
import { OwnJwtService } from 'src/modules/jwt/jwt.service';
#Module({
imports: [
NestJwtModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
signOptions: {
expiresIn: process.env.JWT_EXPIRES_IN,
},
secret: process.env.JWT_SECRET,
secretOrPrivateKey: process.env.JWT_SECRET,
}),
inject: [ConfigService],
}),
],
providers: [OwnJwtService],
exports: [OwnJwtService],
})
export class JwtModule {}
but it doesn't work for me, and I have similar errors:
Error: Nest can't resolve dependencies of the OwnJwtService (?). Please make sure that the argument JwtService at index [0] is available in the AuthModule context.
First, notice that the JwtModule basically creates a module based on jsonwebtoken and your custom errors aren't meant to be dealt inside it.
Second, when you use registerAsync you are meant to get your ENV variables with the ConfigService as in configService.get('JWT_SECRET').
Third, your question is inefficient. The JwtModule already does everything you need. You just need to implement it. Again, just think of it as the jsonwebtoken package adapted for Nest. That's it.
On the signup, login and refreshtoken (if existing) routes you sign when you create a new token.
And in your requests middleware you verify.
One kind of a big issue with Nest is its documentation. It doesn't have everything you need. There might be more than one way to verify a route, but the most straightforward is just using Express middleware, as in a typical Express app.
To do this, you need to implement it in the AppModule like this:
#Module(...)
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer): MiddlewareConsumer | void {
consumer.apply(cookieParser(), AuthMiddleware).forRoutes('/');
}
}
In this example, I'm also registering the module cookieParser() because I send the tokens in a cookie. Other cookie modules will do, too. Both the NestModule and the MiddlewareConsumer come from #nestjs/common.
AuthMiddleware is a middleware I made using this skeleton...
export class AuthMiddleware implements NestMiddleware {
constructor(
private readonly configService: ConfigService,
private readonly jwtService: JwtService
) {}
async use(req: Request, res: Response, next: NextFunction) {
const { yourJwtToken } = req.cookies;
const isValidToken = this.jwtService.verify(
yourJwtToken,
this.configService.get('JWT_SECRET'),
);
if (!isValidToken) throw new UnauthorizedException();
// etc...
next();
}
}
Finally, what you might be asking to, is to apply the AuthGuard.
If you use the Passport ones, you need just to follow the documentation to apply them. They already throw errors if you. If you want to change it, just rewrite its methods.
You can also do it manually. Just use the console to generate a guard, and in there you can check authentication context.switchToHttp().getRequest() and return a boolean after checking the credentials and use the constructor to check the permissions if you want.
You might also skip the middleware config from above and implement the logic inside the guard if you will.
Again, I don't really think changing the JwtModule is the best idea here.