From 7770642b80b0f8a329c17b8245d490ef0eb81d03 Mon Sep 17 00:00:00 2001 From: Kallesh B S Date: Thu, 19 Jun 2025 16:59:03 +0530 Subject: [PATCH] auth modified for sliding time --- src/auth/auth.controller.ts | 8 +-- src/auth/auth.service.ts | 92 ++++++++++++++++++++++---- src/guards/jwt-auth.guard.ts | 39 ++++++----- src/interceptors/logout.interceptor.ts | 3 + 4 files changed, 106 insertions(+), 36 deletions(-) diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index 7d33725..3f7a1ea 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -26,7 +26,7 @@ export class AuthController { httpOnly: true, secure: true, // set to true if you're on HTTPS sameSite: 'none', // adjust based on your needs (strict, lax , none) - maxAge: 90 * 1000, // convert seconds to ms if applicable + maxAge: 8 * 60 * 60 * 1000, // convert seconds to ms if applicable }); } @@ -35,7 +35,7 @@ export class AuthController { httpOnly: true, secure: true, // set to true if you're on HTTPS sameSite: 'none', // adjust based on your needs (strict, lax , none) - maxAge: 90 * 1000, // convert seconds to ms if applicable + maxAge: 8 * 60 * 60 * 1000, // convert seconds to ms if applicable }); } @@ -75,7 +75,7 @@ export class AuthController { httpOnly: true, secure: true, // set to true if you're on HTTPS sameSite: 'none', // adjust based on your needs (strict, lax , none) - maxAge: 90 * 1000, // convert seconds to ms if applicable + maxAge: 8 * 60 * 60 * 1000, // convert seconds to ms if applicable path: '/' }); } @@ -85,7 +85,7 @@ export class AuthController { httpOnly: true, secure: true, // set to true if you're on HTTPS sameSite: 'none', // adjust based on your needs (strict, lax , none) - maxAge: 90 * 1000, // convert seconds to ms if applicable + maxAge: 8 * 60 * 60 * 1000, // convert seconds to ms if applicable path: '/' }); } diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 1170bb9..a7e9161 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -53,7 +53,7 @@ export class AuthService { this.TOKEN_URL = `${this.KEYCLOAK_BASE_URL}/token` - this.USERS_URL = `${this.KEYCLOAK_URL}/realms/${this.KEYCLOAK_REALM}/users`; + this.USERS_URL = `${this.KEYCLOAK_URL}/admin/realms/${this.KEYCLOAK_REALM}/users`; } async login(body: AuthLoginDTO) { @@ -132,37 +132,79 @@ export class AuthService { async introspectToken(token: string): Promise { - const params = new URLSearchParams(); - params.append('token', token); - params.append('client_id', this.CLIENT_ID); - params.append('client_secret', this.CLIENT_SECRET); + console.log("from AuthService introspectToken .......................... end"); + let det: any = await jwt.decode(token); - const response = await axios.post(this.TOKENS_INTROSPECT_URL, params, { - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - }); + const { email } = det; - return response.data; + console.log("email : ", email); + + let uid = await this.getUserIdByEmail(email); + + console.log("uid : ", uid); + + const USERINFO_URL = `${this.KEYCLOAK_URL}/admin/realms/${this.KEYCLOAK_REALM}/users/${uid}/sessions`; + + const admintoken = await this.getAdminAccessToken(); + + try { + console.log("url : ", USERINFO_URL); + + const response = await axios.get(USERINFO_URL, { + headers: { + Authorization: `Bearer ${admintoken}` + } + }); + + console.log("active session data : ", response.data); + + console.log("from AuthService decodeToken .......................... end"); + + if (response.data.length > 0) return { active: true }; + else return { active: false } + + } catch (error) { + console.log("Error in introspectToken : ", error.message); + throw new InternalServerErrorException() + } } async decodeToken(token: string): Promise { + console.log("from AuthService decodeToken .......................... start"); + const decodedHeader = jwt.decode(token, { complete: true }); + if (!decodedHeader || typeof decodedHeader !== 'object') { throw new Error('Invalid token format'); } + console.log("✅ token decoded from decodeToken"); + + const kid: any = decodedHeader.header.kid; const publicKey = await this.getPublicKey(kid); + console.log("✅ publicKey got from decodeToken"); + const introspection = await this.introspectToken(token); - console.log("introspection : ", introspection.active, " : so Unauthorized!"); + console.log("✅ introspection got from decodeToken"); if (!introspection.active) { - throw new Error('Unauthorized'); + console.log("❌ introspection active from decodeToken"); + console.log("from AuthService decodeToken .......................... end"); + throw new UnauthorizedException("Authentication Failed") } + console.log("✅ introspection active from decodeToken"); + const verified = jwt.verify(token, publicKey, { algorithms: ['RS256'] }); + + console.log("✅ token verification successufull from decodeToken"); + + console.log("from AuthService decodeToken .......................... end"); + return verified; } @@ -197,6 +239,7 @@ export class AuthService { params.append('client_secret', this.CLIENT_SECRET); params.append('username', username); params.append('password', password); + // params.append('scope', 'openid'); try { const response = await axios.post(this.TOKEN_URL, params, { @@ -225,6 +268,9 @@ export class AuthService { let adminAccessToken = await this.getAdminAccessToken(); + console.log("admin access_token for getting uid : ", adminAccessToken); + console.log("url for uid : ", url); + const options: AxiosRequestConfig = { method: 'GET', url, @@ -239,7 +285,7 @@ export class AuthService { return data[0]?.id; } catch (error) { // console.error(error); - console.log(error.message); + console.log("from getUserIdByEmail : ", error.message); } } @@ -305,7 +351,7 @@ export class AuthService { { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, }); - console.log(adminTokenResponse.data); + // console.log(adminTokenResponse.data); const adminAccessToken = adminTokenResponse.data.access_token; @@ -489,18 +535,28 @@ export class AuthService { } async getTokenFromRefreshToken(req: Request) { + const refreshToken = req.cookies['refresh_token']; if (!refreshToken) { + + console.log("refesh token not present"); + throw new UnauthorizedException('Authentication failed'); + } + console.log("refesh token present : ", refreshToken); + + const params = new URLSearchParams(); params.append('grant_type', 'refresh_token'); params.append('client_id', this.CLIENT_ID); params.append('client_secret', this.CLIENT_SECRET); params.append('refresh_token', refreshToken); + console.log("url for refresh is : ", `${this.KEYCLOAK_URL}/auth/realms/${this.KEYCLOAK_REALM}/protocol/openid-connect/token`); + try { const response = await axios.post(this.TOKEN_URL, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, @@ -508,7 +564,15 @@ export class AuthService { return response.data; } catch (error) { - console.log(error.message); + if (axios.isAxiosError(error) && error.response) { + console.error('🔴 Axios error:', { + status: error.response.status, + data: error.response.data, + }); + } else { + console.error('🔴 Unexpected error:', error.message); + } + console.log("Error while refreshing tokens : ", error.message); throw new UnauthorizedException('Authentication failed'); } } diff --git a/src/guards/jwt-auth.guard.ts b/src/guards/jwt-auth.guard.ts index 55775e6..b47bfbc 100644 --- a/src/guards/jwt-auth.guard.ts +++ b/src/guards/jwt-auth.guard.ts @@ -6,41 +6,44 @@ import { Global, } from '@nestjs/common'; import { AuthService } from 'src/auth/auth.service'; +import * as jwt from 'jsonwebtoken'; +import { InternalServerException } from 'src/exceptions/internalServerError.exception'; +import { UnauthorizedException as UE} from 'src/exceptions/unauthorized.exception'; @Global() @Injectable() export class JwtAuthGuard implements CanActivate { - constructor(private readonly authService: AuthService) {} + constructor(private readonly authService: AuthService) { } async canActivate(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest(); - - // Read token from cookie - console.log('------------------------------'); - let g = await request.cookies - console.log("g : ",g); - console.log(' guard cookies ------------------------------'); - const token = request.cookies?.access_token; if (!token) { - console.log('error from inspect token .....'); - + console.log('❌ No access token found'); throw new UnauthorizedException('Authentication failed'); } + console.log("✅ access_token is present in JwtAuthGuard"); + try { const decoded = await this.authService.decodeToken(token); request.user = decoded; return true; - } catch (err) { - console.log("error in jwt-auth guard below decode : ",err.message); - - throw new UnauthorizedException({ - message: 'Invalid Token', - error: 'Unauthorized', - statusCode: 401, - }); + } catch (error) { + if ( + error instanceof jwt.TokenExpiredError || + error instanceof jwt.JsonWebTokenError + ) { + console.log('❌ Token is invalid or expired'); + throw new UnauthorizedException('Invalid Token'); + } + else if (error instanceof UnauthorizedException || error instanceof UE) { + throw error; + } + console.log(error instanceof UE); + + throw new InternalServerException(); } } } diff --git a/src/interceptors/logout.interceptor.ts b/src/interceptors/logout.interceptor.ts index 5f941c5..f471bb7 100644 --- a/src/interceptors/logout.interceptor.ts +++ b/src/interceptors/logout.interceptor.ts @@ -20,6 +20,9 @@ export class LogoutInterceptor implements NestInterceptor { response.clearCookie('access_token'); response.clearCookie('refresh_token'); + console.log("No tokens found so logging-out"); + + response.status(200).json({ statusCode: 200, message: 'Logged-Out successfully',