Skip to content

Commit

Permalink
Merge pull request #247 from kinde-oss/fix/refresh-token
Browse files Browse the repository at this point in the history
Fix/refresh token
  • Loading branch information
DanielRivers authored Nov 26, 2024
2 parents 98285b9 + bb17e9a commit 9c99f2f
Show file tree
Hide file tree
Showing 15 changed files with 198 additions and 128 deletions.
5 changes: 2 additions & 3 deletions src/authMiddleware/authMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {NextResponse} from 'next/server';
import {config} from '../config/index';
import {type KindeAccessToken, KindeIdToken} from '../../types';
import {jwtDecoder} from '@kinde/jwt-decoder';
import { validateToken } from '../utils/validateToken';
import {validateToken} from '../utils/validateToken';

const handleMiddleware = async (req, options, onSuccess) => {
const {pathname} = req.nextUrl;
Expand Down Expand Up @@ -62,9 +62,8 @@ const handleMiddleware = async (req, options, onSuccess) => {
});
}


if (isTokenValid && customValidationValid) {
return NextResponse.next();;
return NextResponse.next();
}

return NextResponse.redirect(
Expand Down
24 changes: 16 additions & 8 deletions src/handlers/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import {jwtDecoder} from '@kinde/jwt-decoder';
import {KindeAccessToken, KindeIdToken} from '../../types';
import {config} from '../config/index';
import {generateUserObject} from '../utils/generateUserObject';
import { validateToken } from '../utils/validateToken';
import {validateToken} from '@kinde/jwt-validator';
import {refreshTokens} from '../utils/refreshTokens';

/**
*
Expand All @@ -11,27 +12,34 @@ import { validateToken } from '../utils/validateToken';
*/
export const setup = async (routerClient) => {
try {
const accessTokenEncoded =
let accessTokenEncoded =
await routerClient.sessionManager.getSessionItem('access_token');

const isAccessTokenValid = await validateToken({
token: accessTokenEncoded
})
});

if (!isAccessTokenValid) {
throw new Error('Invalid access token');
if (!(await refreshTokens(routerClient.sessionManager))) {
throw new Error('Invalid access token and refresh');
}
accessTokenEncoded =
await routerClient.sessionManager.getSessionItem('access_token');
}

const idTokenEncoded =
let idTokenEncoded =
await routerClient.sessionManager.getSessionItem('id_token');


const isIdTokenValid = await validateToken({
token: idTokenEncoded
})
});

if (!isIdTokenValid) {
throw new Error('Invalid id token');
if (!(await refreshTokens(routerClient.sessionManager))) {
throw new Error('Invalid access token and refresh');
}
idTokenEncoded =
await routerClient.sessionManager.getSessionItem('id_token');
}

const accessToken = jwtDecoder<KindeAccessToken>(accessTokenEncoded);
Expand Down
4 changes: 2 additions & 2 deletions src/routerClients/AppRouterClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ export default class AppRouterClient extends RouterClient {
this.clientConfig
);
this.url = new URL(req.url);

this.req = req;
this.searchParams = req.nextUrl.searchParams;
this.onErrorCallback = options?.onError;
}

async createStore () {
async createStore() {
this.sessionManager = appRouterSessionManager(await cookies());
}

Expand Down
4 changes: 2 additions & 2 deletions src/session/getAccessToken.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {config} from '../config/index';
import {jwtDecoder} from '@kinde/jwt-decoder';
import { getAccessToken } from '../utils/getAccessToken';
import {getAccessToken} from '../utils/getAccessToken';

/**
* @callback getAccessToken
Expand All @@ -17,7 +17,7 @@ import { getAccessToken } from '../utils/getAccessToken';
export const getAccessTokenFactory = (req, res) => async () => {
try {
const accessToken = await getAccessToken(req, res);
return jwtDecoder(accessToken)
return jwtDecoder(accessToken);
} catch (err) {
if (config.isDebugMode) {
console.error(err);
Expand Down
2 changes: 1 addition & 1 deletion src/session/getIdToken.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {sessionManager} from './sessionManager';
import {config} from '../config/index';
import {jwtDecoder} from '@kinde/jwt-decoder';
import { getIdToken } from '../utils/getIdToken';
import {getIdToken} from '../utils/getIdToken';

/**
* @callback getIdToken
Expand Down
12 changes: 6 additions & 6 deletions src/session/getOrganization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {KindeAccessToken, KindeIdToken} from '../../types';
import {config} from '../config/index';
import {generateOrganizationObject} from '../utils/generateOrganizationObject';
import {sessionManager} from './sessionManager';
import { getAccessToken } from '../utils/getAccessToken';
import {getAccessToken} from '../utils/getAccessToken';
/**
* @callback getOrganization
* @returns {Promise<import('../../types').KindeOrganization | null>}
Expand All @@ -17,16 +17,16 @@ import { getAccessToken } from '../utils/getAccessToken';
*/
export const getOrganizationFactory = (req, res) => async () => {
try {
const idTokenString = await (await sessionManager(req, res)).getSessionItem(
'id_token'
);
const idTokenString = await (
await sessionManager(req, res)
).getSessionItem('id_token');
if (!idTokenString) {
throw new Error('ID token is missing');
}
const idToken = jwtDecoder<KindeIdToken>(idTokenString as string);

const accessToken = await getAccessToken(req, res);
const decodedToken = jwtDecoder<KindeAccessToken>(accessToken)
const accessToken = (await getAccessToken(req, res)) as string;
const decodedToken = jwtDecoder<KindeAccessToken>(accessToken);

return generateOrganizationObject(idToken, decodedToken);
} catch (error) {
Expand Down
8 changes: 4 additions & 4 deletions src/session/getUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import {config} from '../config/index';
import {generateUserObject} from '../utils/generateUserObject';
import {sessionManager} from './sessionManager';
import {jwtDecoder} from '@kinde/jwt-decoder';
import { getAccessToken } from '../utils/getAccessToken';
import { getIdToken } from '../utils/getIdToken';
import {getAccessToken} from '../utils/getAccessToken';
import {getIdToken} from '../utils/getIdToken';

export const getUserFactory =
(req: NextApiRequest, res: NextApiResponse) =>
Expand All @@ -15,13 +15,13 @@ export const getUserFactory =
if (!rawToken) {
return null;
}
const idToken = jwtDecoder<KindeIdToken>(rawToken);
const idToken = jwtDecoder<KindeIdToken>(rawToken as string);

const accessToken = await getAccessToken(req, res);
if (!accessToken) {
return null;
}
const decodedToken = jwtDecoder<KindeAccessToken>(accessToken)
const decodedToken = jwtDecoder<KindeAccessToken>(accessToken as string);

return generateUserObject(idToken, decodedToken) as KindeUser<T>;
} catch (error) {
Expand Down
2 changes: 1 addition & 1 deletion src/session/isAuthenticated.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {getUserFactory} from './getUser';
import { getAccessToken } from '../utils/getAccessToken';
import {getAccessToken} from '../utils/getAccessToken';

/**
*
Expand Down
2 changes: 1 addition & 1 deletion src/session/sessionManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const splitString = (str, length) => {
if (length <= 0) {
return [];
}
return str.match(new RegExp(`.{1,${length}}`, 'g')) || [];
return str.match(new RegExp(`.{1,${length}}`, 'g')) || [];
};

/**
Expand Down
8 changes: 4 additions & 4 deletions src/utils/generateOrganizationObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ export const generateOrganizationObject = (
idToken: KindeIdToken,
accessToken: KindeAccessToken
) => {
if (!accessToken.org_code || !accessToken.org_name) {
throw new Error('Missing required organization fields in access token');
}
if (!accessToken.org_code || !accessToken.org_name) {
throw new Error('Missing required organization fields in access token');
}

return {
orgCode: accessToken.org_code,
orgName: accessToken.org_name,
Expand Down
64 changes: 36 additions & 28 deletions src/utils/getAccessToken.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,44 @@
import {config} from '../config';
import {sessionManager} from '../session/sessionManager';
import { NextApiRequest, NextApiResponse } from "next";
import { validateToken } from './validateToken';
import {NextApiRequest, NextApiResponse} from 'next';
import {kindeClient} from '../session/kindeServerClient';
import {validateToken} from './validateToken';

export const getAccessToken = async(req: NextApiRequest, res: NextApiResponse) => {
try {
const session = await sessionManager(req, res);
const token = await session.getSessionItem('access_token');
export const getAccessToken = async (
req: NextApiRequest,
res: NextApiResponse
) => {
try {
const session = await sessionManager(req, res);
const token = await session.getSessionItem('access_token');

if (!token || typeof token !== 'string') {
if (config.isDebugMode) {
console.error('getAccessToken: invalid token or token is missing');
}
return null;
}
if (!token || typeof token !== 'string') {
if (config.isDebugMode) {
console.error('getAccessToken: invalid token or token is missing');
}
return null;
}

const isTokenValid = await validateToken({
token
});
const isTokenValid = await validateToken({
token
});

if (!isTokenValid) {
if (config.isDebugMode) {
console.error('getAccessToken: invalid token');
}
return null;
}
if (!isTokenValid) {
// look for refresh token
if (await kindeClient.refreshTokens(session)) {
return await session.getSessionItem('access_token');
}

return token
} catch (error) {
if (config.isDebugMode) {
console.error('getAccessToken', error);
}
return null;
if (config.isDebugMode) {
console.error('getAccessToken: invalid token');
}
return null;
}
return token;
} catch (error) {
if (config.isDebugMode) {
console.error('getAccessToken', error);
}
}
return null;
}
};
72 changes: 46 additions & 26 deletions src/utils/getIdToken.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,56 @@
import {config} from '../config';
import {sessionManager} from '../session/sessionManager';
import { NextApiRequest, NextApiResponse } from "next";
import { validateToken } from './validateToken';
import {NextApiRequest, NextApiResponse} from 'next';
import {validateToken} from './validateToken';
import {kindeClient} from '../session/kindeServerClient';

export const getIdToken = async(req: NextApiRequest, res: NextApiResponse) => {
try {
const session = await sessionManager(req, res);
const token = await session.getSessionItem('id_token');
export const getIdToken = async (req: NextApiRequest, res: NextApiResponse) => {
const tokenKey = 'id_token';
try {
const session = await sessionManager(req, res);
const token = await session.getSessionItem(tokenKey);

if (!token || typeof token !== 'string') {
if (config.isDebugMode) {
console.error('getIdToken: invalid token or token is missing');
}
return null;
}
if (!token || typeof token !== 'string') {
if (config.isDebugMode) {
console.error('getIdToken: invalid token or token is missing');
}
return null;
}

const isTokenValid = await validateToken({
token
});
const isTokenValid = await validateToken({
token
});

if (!isTokenValid) {
if (config.isDebugMode) {
console.error('getIdToken: invalid token');
}
return null;
if (!isTokenValid) {
try {
const refreshSuccess = await kindeClient.refreshTokens(session);
if (refreshSuccess) {
const newToken = await session.getSessionItem(tokenKey);
const isNewTokenValid = await validateToken({token: newToken as string});
if (isNewTokenValid) {
return newToken;
}
}

return token
} catch (error) {
if (config.isDebugMode) {
console.error('getIdToken', error);
console.error('getIdToken: token refresh failed');
}
} catch (error) {
if (config.isDebugMode) {
console.error('getIdToken: error during token refresh', error);
}
return null;
}

if (config.isDebugMode) {
console.error('getIdToken: invalid token');
}
return null;
}

return token;
} catch (error) {
if (config.isDebugMode) {
console.error('getIdToken', error);
}
}
return null;
}
};
32 changes: 32 additions & 0 deletions src/utils/refreshTokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {validateToken} from '@kinde/jwt-validator';
import {config} from '../config';
import {kindeClient} from '../session/kindeServerClient';
import {SessionManager} from '@kinde-oss/kinde-typescript-sdk';

export const refreshTokens = async (
session: SessionManager
): Promise<boolean> => {
try {
const refreshResult = await kindeClient.refreshTokens(session);
if (!refreshResult) {
return false;
}

const token = await session.getSessionItem('access_token');

if (token && typeof token === 'string') {
const validationResult = await validateToken({
token,
domain: config.issuerURL
});

return validationResult.valid;
}
return false;
} catch (error) {
if (config.isDebugMode) {
console.error('refreshTokens', error);
}
return false;
}
};
Loading

0 comments on commit 9c99f2f

Please sign in to comment.