how to recognized jwt token from different tenant nestjs muti-tenant jwt - jwt

I implement multi-tenant by multiple databases and use jwt token as authorization, my concern is that when user 1 of tenant 2 login and get the jwt token, when he uses to token to access another tenant, does he recognized as user 1 of tenant 2? If so, how can we fix it?
My Strategy
jwt.strategy.ts
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
private readonly configService: ConfigService,
private readonly moduleRef: ModuleRef,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
passReqToCallback: true,
secretOrKey: configService.get('JWT_SECRET_KEY'),
});
}
async validate(request: Request, jwtPayload: JwtPayload) {
const contextId = ContextIdFactory.getByRequest(request);
const authService: AuthService = await this.moduleRef.resolve(
AuthService,
contextId,
);
let { iat, exp } = jwtPayload;
const timeDiff = exp - iat;
if (timeDiff <= 0) {
throw new UnauthorizedException();
}
return jwtPayload;
}
}
My Auth Service
auth.service.ts
#Injectable({ scope: Scope.REQUEST })
export class AuthService {
constructor(
private readonly jwtService: JwtService,
private readonly configService: ConfigService,
private readonly userService: UsersService,
private readonly auctionHouseService: AuctionHouseService,
) {}
async createToken(user: User) {
let plainUser: any = Object.assign({}, user);
plainUser.auctionHouseId = (
await this.auctionHouseService.getCurrentAuctionHouse()
).id;
return {
expiresIn: this.configService.get('JWT_EXPIRATION_TIME'),
accessToken: this.jwtService.sign(plainUser),
};
}
}
My Login controller
auth.controller.ts
#Controller('api/auth')
#ApiUseTags('authentication')
export class AuthController {
constructor(
private readonly authService: AuthService,
private readonly userService: UsersService,
) {}
#Post('login')
#ApiResponse({ status: 201, description: 'Successful Login' })
#ApiResponse({ status: 400, description: 'Bad Request' })
#ApiResponse({ status: 401, description: 'Unauthorized' })
async login(#Body() payload: LoginPayload, #Req() req): Promise<any> {
let user = await this.authService.validateUser(payload);
return omitPassword(await this.authService.createToken(user));
}

In general, the correct way to identify the domain within which a JWT is valid is the aud (or audience) field. Per RFC7519:
The "aud" (audience) claim identifies the recipients that the JWT is
intended for. Each principal intended to process the JWT MUST
identify itself with a value in the audience claim. If the principal
processing the claim does not identify itself with a value in the
"aud" claim when this claim is present, then the JWT MUST be
rejected. In the general case, the "aud" value is an array of case-
sensitive strings, each containing a StringOrURI value. In the
special case when the JWT has one audience, the "aud" value MAY be a
single case-sensitive string containing a StringOrURI value. The
interpretation of audience values is generally application specific.
Use of this claim is OPTIONAL.
In your context, you'd want to select unique audiences for each of your tenants (let's say it's tenant1 and tenant2) and when generating your JWT you'd do something like this:
plainUser.aud = this.configService.get("JWT_TENANT")
When verifying the token, you would then introduce a check which confirms that the aud claim matches the current tenant.
let { iat, exp, aud } = jwtPayload;
if (aud !== this.configService.get("JWT_TENANT")) {
throw new UnauthorizedException();
}
// The rest of your validation code
NOTE I'm assuming you would run multiple application instances of your application with different configurations for each tenant, however if tenants are derived elsewhere then you should gather that information from the relevant data source instead.
Approaching it this way will ensure that you can identify the user and tenant that the user belongs to and prevent tokens from tenant1 being used to access tenant2.

Related

how to add a token head to a request using HttpClient from IHttpClientFactory in Blazor

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
});
}

keep getting internal server error with nest.js jwt

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

Auth JWT in decorator in NESTJS

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

Can I force a logout or expiration of a JWT token?

For authentication currently we are using JWT, so once a token is created it's created for a lifetime, and if we set a time expire, the token will expire.
Is there any way to expire token?
While clicking log out button, I need to destroy the token.
I'm using ASP.NET Core WebAPI.
I think cancelling JWT is the best way to handle logout. Piotr explained well in his blog: Cancel JWT tokens
We will start with the interface:
public interface ITokenManager
{
Task<bool> IsCurrentActiveToken();
Task DeactivateCurrentAsync();
Task<bool> IsActiveAsync(string token);
Task DeactivateAsync(string token);
}
And process with its implementation, where the basic idea is to keep
track of deactivated tokens only and remove them from a cache when not
needed anymore (meaning when the expiry time passed) – they will be no
longer valid anyway.
public class TokenManager : ITokenManager
{
private readonly IDistributedCache _cache;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IOptions<JwtOptions> _jwtOptions;
public TokenManager(IDistributedCache cache,
IHttpContextAccessor httpContextAccessor,
IOptions<JwtOptions> jwtOptions
)
{
_cache = cache;
_httpContextAccessor = httpContextAccessor;
_jwtOptions = jwtOptions;
}
public async Task<bool> IsCurrentActiveToken()
=> await IsActiveAsync(GetCurrentAsync());
public async Task DeactivateCurrentAsync()
=> await DeactivateAsync(GetCurrentAsync());
public async Task<bool> IsActiveAsync(string token)
=> await _cache.GetStringAsync(GetKey(token)) == null;
public async Task DeactivateAsync(string token)
=> await _cache.SetStringAsync(GetKey(token),
" ", new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromMinutes(_jwtOptions.Value.ExpiryMinutes)
});
private string GetCurrentAsync()
{
var authorizationHeader = _httpContextAccessor
.HttpContext.Request.Headers["authorization"];
return authorizationHeader == StringValues.Empty
? string.Empty
: authorizationHeader.Single().Split(" ").Last();
}
private static string GetKey(string token)
=> $"tokens:{token}:deactivated";
}
As you can see, there are 2 helper methods that will use the current
HttpContext in order to make things even easier.
Next, let’s create a middleware that will check if the token was
deactivated or not. That’s the reason why we should keep them in cache
– hitting the database with every request instead would probably kill
your app sooner or later (or at least make it really, really slow):
public class TokenManagerMiddleware : IMiddleware
{
private readonly ITokenManager _tokenManager;
public TokenManagerMiddleware(ITokenManager tokenManager)
{
_tokenManager = tokenManager;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
if (await _tokenManager.IsCurrentActiveToken())
{
await next(context);
return;
}
context.Response.StatusCode = (int) HttpStatusCode.Unauthorized;
}
}
Eventually, let’s finish our journey with implementing an endpoint for
canceling the tokens:
[HttpPost("tokens/cancel")]
public async Task<IActionResult> CancelAccessToken()
{
await _tokenManager.DeactivateCurrentAsync();
return NoContent();
}
For sure, we could make it more sophisticated, via passing the token
via URL, or by canceling all of the existing user tokens at once
(which would require an additional implementation to keep track of
them), yet this is a basic sample that just works.
Make sure that you will register the required dependencies in your
container and configure the middleware:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddTransient<TokenManagerMiddleware>();
services.AddTransient<ITokenManager, Services.TokenManager>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddDistributedRedisCache(r => { r.Configuration = Configuration["redis:connectionString"];
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
...
app.UseAuthentication();
app.UseMiddleware<TokenManagerMiddleware>();
app.UseMvc();
}
And provide a configuration for Redis in appsettings.json file:
"redis": {
"connectionString": "localhost"
}
Try to run the application now and invoke the token cancellation[sic]
endpoint – that’s it.
Actually the best way to logout is just remove token from the client. And you can make lifetime of tokens short (5-15 minutes) and implement refresh tokens for additions security. In this case there are less chance for attacker to do something with your JWT
If you have implemented the login scenario with the refresh token, You can remove the refresh token from the server and then , and then you should remove the token from the client.

WebTestClient mutateWith apparently not mutating

My (Cucumber) BDD unit test using WebTestClient is failing (with a 403 Forbidden), when I believe it should be passing. After some debugging, I established that this is because the CSRF check is failing, which suggests the mutateWith(csrf()) operation is not working. What am I doing wrong?
My test scenario:
Scenario Outline: Login
Given that player "<player>" exists with password "<password>"
And presenting a valid CSRF token
When log in as "<player>" using password "<password>"
Then program accepts the login
My test steps code (note the presence of client.mutateWith(csrf())):
#SpringBootTest(...)
#AutoConfigureWebTestClient
public class WebSteps {
#Autowired
private WebTestClient client;
...
private WebTestClient.ResponseSpec response;
#Given("presenting a valid CSRF token")
public void presenting_a_valid_CSRF_token() {
client.mutateWith(csrf());
}
#When("log in as {string} using password {string}")
public void log_in_as_using_password(final String player,
final String password) {
response = client.post().uri("/login")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(BodyInserters.fromFormData("username", player)
.with("password", password))
.exchange();
}
#Then("program accepts the login")
public void program_accepts_the_login() {
response.expectStatus().isFound().expectHeader().valueEquals("Location",
"/");
}
...
Despite its name, the mutateWith() method does not really mutate its object. Rather, it returns a new object that has had the mutation applied. Therefore instead of writing
#Given("presenting a valid CSRF token")
public void presenting_a_valid_CSRF_token() {
client.mutateWith(csrf());
}
write
#Given("presenting a valid CSRF token")
public void presenting_a_valid_CSRF_token() {
client = client.mutateWith(csrf());
}
This error is more likely to occur in a Cucumber test because of the way that test steps alter shared state (The client object), rather than use a fluent API with a long chain of calls.