Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

react-oauth2-code-pkce

Package Overview
Dependencies
Maintainers
1
Versions
79
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

react-oauth2-code-pkce - npm Package Compare versions

Comparing version 1.2.5 to 1.3.0-alpha.1

70

dist/AuthContext.js

@@ -34,2 +34,3 @@ "use strict";

const validateAuthConfig_1 = require("./validateAuthConfig");
const FALLBACK_EXPIRE_TIME = 600; // 10minutes
exports.AuthContext = (0, react_1.createContext)({

@@ -41,4 +42,6 @@ token: '',

const AuthProvider = ({ authConfig, children }) => {
const [refreshToken, setRefreshToken] = (0, Hooks_1.default)('ROCP_refreshToken', null);
const [refreshToken, setRefreshToken] = (0, Hooks_1.default)('ROCP_refreshToken', undefined);
const [refreshTokenExpire, setRefreshTokenExpire] = (0, Hooks_1.default)('ROCP_refreshTokenExpire', (0, authentication_1.timeOfExpire)(FALLBACK_EXPIRE_TIME));
const [token, setToken] = (0, Hooks_1.default)('ROCP_token', '');
const [tokenExpire, setTokenExpire] = (0, Hooks_1.default)('ROCP_tokenExpire', (0, authentication_1.timeOfExpire)(FALLBACK_EXPIRE_TIME));
const [idToken, setIdToken] = (0, Hooks_1.default)('ROCP_idToken', undefined);

@@ -49,6 +52,17 @@ const [loginInProgress, setLoginInProgress] = (0, Hooks_1.default)('ROCP_loginInProgress', false);

let interval;
(0, validateAuthConfig_1.validateAuthConfig)(authConfig);
// Set default values and override from passed config
const { decodeToken = true, scope = '', preLogin = () => null, postLogin = () => null } = authConfig;
const config = {
decodeToken: decodeToken,
scope: scope,
preLogin: preLogin,
postLogin: postLogin,
...authConfig,
};
(0, validateAuthConfig_1.validateAuthConfig)(config);
function logOut() {
setRefreshToken(null);
setRefreshToken(undefined);
setToken('');
setTokenExpire((0, authentication_1.timeOfExpire)(FALLBACK_EXPIRE_TIME));
setRefreshTokenExpire((0, authentication_1.timeOfExpire)(FALLBACK_EXPIRE_TIME));
setIdToken(undefined);

@@ -59,13 +73,20 @@ setTokenData(undefined);

function handleTokenResponse(response) {
setRefreshToken(response.refresh_token);
setRefreshToken(response?.refresh_token);
setToken(response.access_token);
setIdToken(response?.id_token || 'None');
setTokenExpire((0, authentication_1.timeOfExpire)(response.expires_in || FALLBACK_EXPIRE_TIME));
setRefreshTokenExpire((0, authentication_1.timeOfExpire)(response.refresh_token_expires_in || FALLBACK_EXPIRE_TIME));
setIdToken(response?.id_token);
setLoginInProgress(false);
setTokenData((0, authentication_1.decodeToken)(response.access_token));
try {
if (config.decodeToken)
setTokenData((0, authentication_1.decodeJWT)(response.access_token));
}
catch (e) {
setError(e.message);
}
}
function refreshAccessToken() {
if (refreshToken) {
if (token && (0, authentication_1.tokenExpired)(token)) {
// The client has an expired token. Will try to get a new one with the refreshToken
(0, authentication_1.fetchWithRefreshToken)({ authConfig, refreshToken })
if (token && (0, authentication_1.tokenExpired)(tokenExpire)) {
if (refreshToken && !(0, authentication_1.tokenExpired)(refreshTokenExpire)) {
(0, authentication_1.fetchWithRefreshToken)({ config, refreshToken })
.then((result) => handleTokenResponse(result))

@@ -76,12 +97,12 @@ .catch((error) => {

logOut();
(0, authentication_1.login)(authConfig);
(0, authentication_1.logIn)(config);
}
});
}
else {
// The refresh token has expired. Need to log in from scratch.
logOut();
(0, authentication_1.logIn)(config);
}
}
else {
// No refresh_token
console.error('Tried to refresh access_token without a refresh_token.');
setError('Bad authorization state. Refreshing the page might solve the issue.');
}
}

@@ -107,3 +128,3 @@ // Register the 'check for soon expiring access token' interval (Every minute)

// Request token from auth server with the auth code
(0, authentication_1.fetchTokens)(authConfig)
(0, authentication_1.fetchTokens)(config)
.then((tokens) => {

@@ -113,4 +134,4 @@ handleTokenResponse(tokens);

// Call any postLogin function in authConfig
if (authConfig?.postLogin)
authConfig.postLogin();
if (config?.postLogin)
config.postLogin();
})

@@ -125,6 +146,13 @@ .catch((error) => {

setLoginInProgress(true);
(0, authentication_1.login)(authConfig);
(0, authentication_1.logIn)(config);
}
else {
setTokenData((0, authentication_1.decodeToken)(token));
if (decodeToken) {
try {
setTokenData((0, authentication_1.decodeJWT)(token));
}
catch (e) {
setError(e.message);
}
}
refreshAccessToken(); // Check if token should be updated

@@ -131,0 +159,0 @@ }

@@ -1,18 +0,19 @@

import { TAuthConfig, TTokenData, TTokenResponse } from './Types';
import { TInternalConfig, TTokenData, TTokenResponse } from './Types';
export declare const EXPIRED_REFRESH_TOKEN_ERROR_CODES: string[];
export declare function login(authConfig: TAuthConfig): Promise<void>;
export declare const fetchTokens: (authConfig: TAuthConfig) => Promise<TTokenResponse>;
export declare function logIn(config: TInternalConfig): Promise<void>;
export declare const fetchTokens: (config: TInternalConfig) => Promise<TTokenResponse>;
export declare const fetchWithRefreshToken: (props: {
authConfig: TAuthConfig;
config: TInternalConfig;
refreshToken: string;
}) => Promise<TTokenResponse>;
/**
* Decodes the the base64 encoded JWT. Returns a TToken.
* Decodes the base64 encoded JWT. Returns a TToken.
*/
export declare const decodeToken: (token: string) => TTokenData;
export declare const decodeJWT: (token: string) => TTokenData;
export declare const timeOfExpire: (validTimeDelta: number) => number;
/**
* Check if the Access Token has expired by looking at the 'exp' JWT header.
* Will return True if the token has expired, OR there is less than 10min until it expires.
* Check if the Access Token has expired.
* Will return True if the token has expired, OR there is less than 5min until it expires.
*/
export declare const tokenExpired: (token: string) => boolean;
export declare function tokenExpired(tokenExpire: number): boolean;
export declare const errorMessageForExpiredRefreshToken: (errorMessage: string) => boolean;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.errorMessageForExpiredRefreshToken = exports.tokenExpired = exports.decodeToken = exports.fetchWithRefreshToken = exports.fetchTokens = exports.login = exports.EXPIRED_REFRESH_TOKEN_ERROR_CODES = void 0;
exports.errorMessageForExpiredRefreshToken = exports.tokenExpired = exports.timeOfExpire = exports.decodeJWT = exports.fetchWithRefreshToken = exports.fetchTokens = exports.logIn = exports.EXPIRED_REFRESH_TOKEN_ERROR_CODES = void 0;
const pkceUtils_1 = require("./pkceUtils");

@@ -8,3 +8,3 @@ const codeVerifierStorageKey = 'PKCE_code_verifier';

exports.EXPIRED_REFRESH_TOKEN_ERROR_CODES = ['AADSTS700084'];
async function login(authConfig) {
async function logIn(config) {
// Create and store a random string in localStorage, used as the 'code_verifier'

@@ -18,5 +18,5 @@ const codeVerifier = (0, pkceUtils_1.generateRandomString)(40);

response_type: 'code',
client_id: authConfig.clientId,
scope: authConfig.scope || '',
redirect_uri: authConfig.redirectUri,
client_id: config.clientId,
scope: config.scope,
redirect_uri: config.redirectUri,
code_challenge: codeChallenge,

@@ -26,8 +26,8 @@ code_challenge_method: 'S256',

// Call any preLogin function in authConfig
if (authConfig?.preLogin)
authConfig.preLogin();
window.location.replace(`${authConfig.authorizationEndpoint}?${params.toString()}`);
if (config?.preLogin)
config.preLogin();
window.location.replace(`${config.authorizationEndpoint}?${params.toString()}`);
});
}
exports.login = login;
exports.logIn = logIn;
// This is called a "type predicate". Which allow use to know which kind of response we got, in a type safe way.

@@ -41,18 +41,19 @@ function isTokenResponse(body) {

body: formData,
})
.then((response) => response.json().then((body) => {
if (isTokenResponse(body)) {
return body;
}).then((response) => {
if (!response.ok) {
console.error(response);
throw Error(response.statusText);
}
else {
console.error(body.error_description);
throw body.error_description;
}
}))
.catch((error) => {
console.error(error);
throw error.message;
return response.json().then((body) => {
if (isTokenResponse(body)) {
return body;
}
else {
console.error(body);
throw Error(body.error_description);
}
});
});
}
const fetchTokens = (authConfig) => {
const fetchTokens = (config) => {
/*

@@ -75,45 +76,55 @@ The browser has been redirected from the authentication endpoint with

formData.append('code', authCode);
formData.append('scope', authConfig.scope || '');
formData.append('client_id', authConfig.clientId);
formData.append('redirect_uri', authConfig.redirectUri);
formData.append('scope', config.scope);
formData.append('client_id', config.clientId);
formData.append('redirect_uri', config.redirectUri);
formData.append('code_verifier', codeVerifier);
return postWithFormData(authConfig.tokenEndpoint, formData);
return postWithFormData(config.tokenEndpoint, formData);
};
exports.fetchTokens = fetchTokens;
const fetchWithRefreshToken = (props) => {
const { authConfig, refreshToken } = props;
const { config, refreshToken } = props;
const formData = new FormData();
formData.append('grant_type', 'refresh_token');
formData.append('refresh_token', refreshToken);
formData.append('scope', authConfig.scope || '');
formData.append('client_id', authConfig.clientId);
formData.append('redirect_uri', authConfig.redirectUri);
return postWithFormData(authConfig.tokenEndpoint, formData);
formData.append('scope', config.scope);
formData.append('client_id', config.clientId);
formData.append('redirect_uri', config.redirectUri);
return postWithFormData(config.tokenEndpoint, formData);
};
exports.fetchWithRefreshToken = fetchWithRefreshToken;
/**
* Decodes the the base64 encoded JWT. Returns a TToken.
* Decodes the base64 encoded JWT. Returns a TToken.
*/
const decodeToken = (token) => {
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(atob(base64)
.split('')
.map(function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
})
.join(''));
return JSON.parse(jsonPayload);
const decodeJWT = (token) => {
try {
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(atob(base64)
.split('')
.map(function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
})
.join(''));
return JSON.parse(jsonPayload);
}
catch (e) {
console.error(e);
throw Error('Failed to decode the access token.\n\tIs it a proper Java Web Token?\n\t' +
"You can disable JWT decoding by setting the 'decodeToken' value to 'false' the configuration.");
}
};
exports.decodeToken = decodeToken;
exports.decodeJWT = decodeJWT;
// Returns epoch time (in seconds) for when the token will expire
const timeOfExpire = (validTimeDelta) => Math.round(Date.now() / 1000 + validTimeDelta);
exports.timeOfExpire = timeOfExpire;
/**
* Check if the Access Token has expired by looking at the 'exp' JWT header.
* Will return True if the token has expired, OR there is less than 10min until it expires.
* Check if the Access Token has expired.
* Will return True if the token has expired, OR there is less than 5min until it expires.
*/
const tokenExpired = (token) => {
const bufferTimeInMilliseconds = 10 * 60 * 1000; // minutes * seconds * toMilliseconds
const { exp } = (0, exports.decodeToken)(token);
const expirationTimeWithBuffer = new Date(exp * 1000 - bufferTimeInMilliseconds);
return new Date() > expirationTimeWithBuffer;
};
function tokenExpired(tokenExpire) {
const now = Math.round(Date.now()) / 1000;
const bufferTimeInSeconds = 5 * 60; // minutes * seconds
const nowWithBuffer = now + bufferTimeInSeconds;
return nowWithBuffer >= tokenExpire;
}
exports.tokenExpired = tokenExpired;

@@ -120,0 +131,0 @@ const errorMessageForExpiredRefreshToken = (errorMessage) => {

@@ -6,8 +6,7 @@ "use strict";

const [storedValue, setStoredValue] = (0, react_1.useState)(() => {
const item = localStorage.getItem(key);
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
}
catch (error) {
console.log(error);
return initialValue;

@@ -23,3 +22,3 @@ }

catch (error) {
console.log(error);
console.log(`Failed to store value '${value}' for key '${key}'`);
}

@@ -26,0 +25,0 @@ };

@@ -6,2 +6,11 @@ import { ReactNode } from 'react';

};
export declare type TTokenResponse = {
access_token: string;
scope: string;
token_type: string;
expires_in?: number;
refresh_token?: string;
refresh_token_expires_in?: number;
id_token?: string;
};
export interface IAuthProvider {

@@ -28,9 +37,4 @@ authConfig: TAuthConfig;

postLogin?: () => void;
decodeToken?: boolean;
};
export declare type TTokenResponse = {
access_token: string;
refresh_token: string;
expires_in: number;
id_token?: string;
};
export declare type TAzureADErrorResponse = {

@@ -40,1 +44,11 @@ error_description: string;

};
export declare type TInternalConfig = {
clientId: string;
authorizationEndpoint: string;
tokenEndpoint: string;
redirectUri: string;
scope: string;
preLogin?: () => void;
postLogin?: () => void;
decodeToken: boolean;
};

@@ -1,2 +0,2 @@

import { TAuthConfig } from './Types';
export declare function validateAuthConfig(authConfig: TAuthConfig): void;
import { TInternalConfig } from './Types';
export declare function validateAuthConfig(config: TInternalConfig): void;

@@ -8,12 +8,12 @@ "use strict";

}
function validateAuthConfig(authConfig) {
if (stringIsUnset(authConfig?.clientId))
function validateAuthConfig(config) {
if (stringIsUnset(config?.clientId))
throw Error("'clientId' must be set in the 'AuthConfig' object passed to 'react-oauth2-code-pkce' AuthProvider");
if (stringIsUnset(authConfig?.authorizationEndpoint))
if (stringIsUnset(config?.authorizationEndpoint))
throw Error("'authorizationEndpoint' must be set in the 'AuthConfig' object passed to 'react-oauth2-code-pkce' AuthProvider");
if (stringIsUnset(authConfig?.tokenEndpoint))
if (stringIsUnset(config?.tokenEndpoint))
throw Error("'tokenEndpoint' must be set in the 'AuthConfig' object passed to 'react-oauth2-code-pkce' AuthProvider");
if (stringIsUnset(authConfig?.redirectUri))
if (stringIsUnset(config?.redirectUri))
throw Error("'redirectUri' must be set in the 'AuthConfig' object passed to 'react-oauth2-code-pkce' AuthProvider");
}
exports.validateAuthConfig = validateAuthConfig;
{
"name": "react-oauth2-code-pkce",
"version": "1.2.5",
"version": "1.3.0-alpha.1",
"description": "Plug-and-play react package for OAuth2 Authorization Code flow with PKCE",

@@ -16,3 +16,3 @@ "main": "dist/index.js",

"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"test": "ts-jest",
"start": "yarn run start"

@@ -19,0 +19,0 @@ },

@@ -16,2 +16,10 @@ # react-oauth2-pkce

## Features
- Authorization server agnostic, works equally well with all OAuth2 auth servers following the OAuth2 spec
- Supports OpenID Connect (idTokens)
- Pre- and Post login callbacks
- Silently refreshes short lived access tokens in the background
- Decodes JWT's
## Example

@@ -22,20 +30,26 @@

import ReactDOM from 'react-dom'
import { AuthContext, AuthProvider } from "react-oauth2-code-pkce"
import { AuthContext, AuthProvider, TAuthConfig } from "react-oauth2-code-pkce"
const authConfig = {
const authConfig: TAuthConfig = {
clientId: 'myClientID',
authorizationEndpoint: 'myAuthEndpoint',
tokenEndpoint: 'myTokenEndpoint',
// Where ever your application is running. Must match whats configured in authorization server
// Whereever your application is running. Must match configuration on authorization server
redirectUri: 'http://localhost:3000/',
// Optional
scope: 'someScope',
scope: 'someScope openid',
// Optional
logoutEndpoint: '',
// Optional
logoutRedirect: ''
logoutRedirect: '',
// Example to redirect back to original path after login has completed
preLogin: () => localStorage.setItem('preLoginPath', location.pathname),
postLogin: () => location.replace(localStorage.getItem('preLoginPath')),
// Wether or not to try and decode the access token.
// Stops errors from being printed in the console for non-JWT access tokens, etc. from Github
decodeToken: true
}
function LoginInfo() {
const { tokenData, token, logOut } = useContext(AuthContext)
const { tokenData, token, idToken, logOut, error } = useContext(AuthContext)

@@ -72,4 +86,22 @@ return (

## Install
The package is available on npmjs.com here; https://www.npmjs.com/package/react-oauth2-code-pkce
```bash
npm install react-oauth2-code-pkce
```
and import
```javascript
import { AuthContext, AuthProvider } from "react-oauth2-code-pkce"
```
## Develop
1. Update the 'authConfig' object in `src/index.js` with config from your authorization server and application
2. Install node_modules -> `$ yarn install`
3. Run -> `$ yarn start`
## Contribute
You are welcome to create issues and pull requests :)
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc