From c63f6dc6c0c363dba046b2b44de4a6642e7a9d95 Mon Sep 17 00:00:00 2001 From: jesusrodrz <34176666+jesusrodrz@users.noreply.github.com> Date: Mon, 4 Apr 2022 09:32:46 -0400 Subject: [PATCH 1/2] improve token validation --- README.md | 3 +++ package-lock.json | 17 +++++++++++++++-- package.json | 3 +++ src/client.ts | 45 +++++++++++++++++++++++++++++++++++++-------- src/types.ts | 24 +++++++++++++++++++++--- src/utils.ts | 12 ++++++++++++ 6 files changed, 91 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 86eb2e2..d02cfbf 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,9 @@ const CREDENTIALS_HANDLER = { await AsyncStorage.removeItem('credentials_store'); apolloClient.resetStore(); }, + // You can specify a what token the library will validate + tokenToValidate: 'accessToken', + // or pass a function that validate it validateToken: (credentials) => { const { exp } = jwtDecode(credentials.idToken) as { exp: number }; diff --git a/package-lock.json b/package-lock.json index 26b07ab..37f9b5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,16 @@ { "name": "@cobuildlab/react-native-auth0", - "version": "0.2.0", + "version": "0.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@cobuildlab/react-native-auth0", - "version": "0.2.0", + "version": "0.3.0", "license": "GPL-3.0", + "dependencies": { + "jwt-decode": "^3.1.2" + }, "devDependencies": { "@babel/core": "^7.9.6", "@babel/preset-env": "^7.9.6", @@ -10796,6 +10799,11 @@ "verror": "1.10.0" } }, + "node_modules/jwt-decode": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", + "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -24476,6 +24484,11 @@ "verror": "1.10.0" } }, + "jwt-decode": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", + "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", diff --git a/package.json b/package.json index f48afb6..927e847 100644 --- a/package.json +++ b/package.json @@ -62,5 +62,8 @@ "prettier --write", "eslint --fix" ] + }, + "dependencies": { + "jwt-decode": "^3.1.2" } } diff --git a/src/client.ts b/src/client.ts index 1198b32..cfb06cf 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,19 +1,25 @@ import Auth0, { AuthorizeOptions, Credentials as Auth0Credentials, - Options, } from 'react-native-auth0'; -import { Credentials, CredentialsHandlersInput, ErrorCases } from './types'; -import { ErrorPublisher } from './utils'; +import { + Credentials, + CredentialsHandlersInput, + ErrorCases, + Options, + tokenToValidateType, +} from './types'; +import { ErrorPublisher, validateToken } from './utils'; export class Auth0Native extends Auth0 { private credentials: Credentials | null = null; + private audience: string; private saveCredentials: CredentialsHandlersInput['save']; private getCredentials: CredentialsHandlersInput['get']; private clearCredentials: CredentialsHandlersInput['clear']; - private validateToken: CredentialsHandlersInput['validateToken']; - + private validateToken?: CredentialsHandlersInput['validateToken']; + private tokenToValidate?: tokenToValidateType; private errors: Record = { AUTHORIZATION: new ErrorPublisher(), CLEAR_SESSION: new ErrorPublisher(), @@ -21,12 +27,22 @@ export class Auth0Native extends Auth0 { SAVE_CREDENTIALS: new ErrorPublisher(), }; + /** + * Create a client to used to authenticate with auth0 platform and manage that auth state. + * + * @param {Options} options - Options. + * @param {CredentialsHandlersInput} credentialsHandlers - Options to handle the credentials and token validations. + */ constructor(options: Options, credentialsHandlers: CredentialsHandlersInput) { super(options); this.saveCredentials = credentialsHandlers.save; this.getCredentials = credentialsHandlers.get; this.clearCredentials = credentialsHandlers.clear; this.validateToken = credentialsHandlers.validateToken; + this.tokenToValidate = credentialsHandlers.tokenToValidate; + + // set a default audience if the options is undefined + this.audience = options.audience || `https://${options.domain}/api/v2/`; } async handleCredentials(data: Auth0Credentials): Promise { @@ -49,8 +65,8 @@ export class Auth0Native extends Auth0 { /** * * @param {string} scope - Scopes requested for the issued tokens. E.g. `openid profile`. - * @param {object} options - Options to pass to the auth endpoint. - * @returns {object} The auth0 credentials. + * @param {AuthorizeOptions} options - Options to pass to the auth endpoint. + * @returns {Promise} The auth0 credentials. */ async authorize( scope: string, @@ -60,6 +76,7 @@ export class Auth0Native extends Auth0 { const result = await this.webAuth.authorize( { scope, + audience: this.audience, }, options, ); @@ -90,8 +107,20 @@ export class Auth0Native extends Auth0 { if (!credentials) { return false; } + let valid = false; + + if (this.validateToken && this.validateToken(credentials)) { + valid = true; + } + + if ( + this.tokenToValidate && + validateToken(credentials[this.tokenToValidate]) + ) { + valid = true; + } - if (this.validateToken(credentials)) { + if (valid) { this.credentials = credentials; return true; } diff --git a/src/types.ts b/src/types.ts index ee9287e..e11b605 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,18 +1,36 @@ import { AuthorizeOptions, Credentials as Auth0Credentials, + Options as Auth0Options, } from 'react-native-auth0'; export interface Credentials extends Auth0Credentials { issuedAt?: number; } -export type CredentialsHandlersInput = { +export interface Options extends Auth0Options { + audience?: string; +} +export type tokenToValidateType = keyof Pick< + Credentials, + 'idToken' | 'accessToken' +>; +interface CredentialsHandlersInputBase { save: (data: Credentials) => void | Promise; clear: () => void | Promise; get: () => (Credentials | null) | Promise; - validateToken: (data: Credentials) => boolean; -}; +} +export type CredentialsHandlersInput = CredentialsHandlersInputBase & + ( + | { + validateToken: (data: Credentials) => boolean; + tokenToValidate?: tokenToValidateType; + } + | { + tokenToValidate: tokenToValidateType; + validateToken?: (data: Credentials) => boolean; + } + ); export interface AuthClientContextType { authorize: (args: { diff --git a/src/utils.ts b/src/utils.ts index f350c5f..33bd1fa 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,3 +1,4 @@ +import jwtDecode from 'jwt-decode'; import { ErrorCallbackType } from './types'; /**. @@ -9,6 +10,17 @@ export function getTimestamp(): number { return Math.round(new Date().getTime() / 1000); } +/** + * @param {string} token - Token to validate. + * @returns {boolean} Boolean if the token is valid. + */ +export function validateToken(token: string): boolean { + const tokenObj = jwtDecode<{ + exp: number; + }>(token); + + return tokenObj.exp > getTimestamp(); +} export class ErrorPublisher { private subscribers: Set = new Set(); From 444f0225229cc7f37c58b017adf30c0197e8bc49 Mon Sep 17 00:00:00 2001 From: jesusrodrz <34176666+jesusrodrz@users.noreply.github.com> Date: Mon, 4 Apr 2022 09:37:28 -0400 Subject: [PATCH 2/2] bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 927e847..ef8b96f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cobuildlab/react-native-auth0", - "version": "0.3.0", + "version": "0.3.1", "description": "Todo", "main": "lib/index.js", "types": "./lib/index.d.ts",