Skip to content
This repository has been archived by the owner on Jul 25, 2024. It is now read-only.

Commit

Permalink
fix (authenticate): multiple fixes to improve security and code style
Browse files Browse the repository at this point in the history
- remove custom check of JWT
- check if the JWT is valid using the private key BEFORE hitting the database
- make functions pure, store changed state after everything has been checked
- added payload type in JWTLoginOptions
  • Loading branch information
maxgalbu committed Oct 28, 2021
1 parent 8ecb493 commit 967c517
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 49 deletions.
10 changes: 10 additions & 0 deletions adonis-typings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,23 @@ declare module "@ioc:Adonis/Addons/Jwt" {
GuardContract,
} from "@ioc:Adonis/Addons/Auth";
import { DateTime } from "luxon";
import { JWTPayload } from "jose/jwt/verify";

export type JWTCustomPayloadData = {
[key: string]: any;
};

export type JWTCustomPayload = JWTPayload & {
data?: JWTCustomPayloadData;
};

/**
* Login options
*/
export type JWTLoginOptions = {
name?: string;
expiresIn?: number | string;
payload?: JWTCustomPayloadData;
[key: string]: any;
};

Expand Down
74 changes: 25 additions & 49 deletions lib/Guards/JwtGuard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ProviderToken } from '@adonisjs/auth/build/src/Tokens/ProviderToken';
import { SignJWT } from 'jose/jwt/sign';
import { jwtVerify } from 'jose/jwt/verify';
import JwtAuthenticationException from '../Exceptions/JwtAuthenticationException';
import { JWTGuardConfig, JWTGuardContract, JWTLoginOptions, JWTTokenContract, JwtTokenProviderContract } from '@ioc:Adonis/Addons/Jwt';
import { JWTGuardConfig, JWTGuardContract, JWTLoginOptions, JWTCustomPayload, JWTCustomPayloadData, JWTTokenContract, JwtTokenProviderContract } from '@ioc:Adonis/Addons/Jwt';

/**
* JWT token represents a persisted token generated for a given user.
Expand Down Expand Up @@ -146,22 +146,23 @@ export class JWTGuard extends BaseGuard<'jwt'> implements JWTGuardContract<any,
this.authenticationAttempted = true;

/**
* Ensure the "Authorization" header value exists
* Ensure the "Authorization" header value exists, and it's a valid JWT
*/
const token = this.getBearerToken();
const tokenValue = this.parsePublicToken(token);
const payload = await this.verifyToken(token);

/**
* Query token and user
*/
const providerToken = await this.getProviderToken(tokenValue);
const providerUser = await this.getUserById(providerToken.userId);
const providerToken = await this.getProviderToken(token);
const providerUser = await this.getUserById(payload.data!, providerToken.userId);

/**
* Marking user as logged in
*/
this.markUserAsLoggedIn(providerUser.user, true);
this.token = providerToken;
this.parsedToken = token;

/**
* Emit authenticate event. It can be used to track user logins.
Expand Down Expand Up @@ -391,33 +392,26 @@ export class JWTGuard extends BaseGuard<'jwt'> implements JWTGuardContract<any,
}

/**
* Parses the token received in the request. The method also performs
* some initial level of sanity checks.
* Verify the token received in the request.
*/
private parsePublicToken(token: string): string {
const parts = token.split('.');
private async verifyToken(token: string): Promise<JWTCustomPayload> {
const secret = this.generateKey(this.config.privateKey);

/**
* Ensure the token has at least three parts
*/
if (parts.length < 3) {
throw new JwtAuthenticationException("Invalid JWT format");
}
const { payload } = await jwtVerify(token, secret, {
issuer: this.config.issuer,
audience: this.config.audience,
});

/**
* Ensure 2nd part of the token has the expected length
*/
const value = parts.join('.');
if (value.length < 30) {
throw new JwtAuthenticationException("Invalid JWT format: token too short");
}
const { data, exp }: JWTCustomPayload = payload;

/**
* Set parsed token
*/
this.parsedToken = token;
if (!data) {
throw new JwtAuthenticationException("Invalid JWT payload");
}
if (exp && exp < Math.floor(DateTime.now().toSeconds())) {
throw new JwtAuthenticationException("Expired JWT token");
}

return token;
return payload;
}

/**
Expand All @@ -440,33 +434,15 @@ export class JWTGuard extends BaseGuard<'jwt'> implements JWTGuardContract<any,
/**
* Returns user from the user session id
*/
private async getUserById(id: string | number) {
const token = this.parsedToken || '';
const secret = this.generateKey(this.config.privateKey);

const { payload } = await jwtVerify(token, secret, {
issuer: this.config.issuer,
audience: this.config.audience,
});

const { data, exp }: any = payload;

if (exp && exp < Math.floor(DateTime.now().toSeconds())) {
throw new JwtAuthenticationException("Expired JWT token");
}

if (!data) {
throw new JwtAuthenticationException("Invalid JWT payload");
}

if (data.userId !== id) {
private async getUserById(payload: JWTCustomPayloadData, id: string | number) {
if (payload.userId !== id) {
throw new JwtAuthenticationException("Invalid user in payload");
}

const authenticatable = await this.provider.findById(data.userId);
const authenticatable = await this.provider.findById(payload.userId);

if (!authenticatable.user) {
throw new JwtAuthenticationException("No user found from paypload");
throw new JwtAuthenticationException("No user found from payload");
}

return authenticatable;
Expand Down

0 comments on commit 967c517

Please sign in to comment.