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

supertokens-website

Package Overview
Dependencies
Maintainers
1
Versions
129
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

supertokens-website - npm Package Compare versions

Comparing version 2.0.1 to 3.0.0

.mocharc.yml

12

handleSessionExp.js

@@ -10,2 +10,3 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {

import Lock from 'browser-tabs-lock';
import { AntiCsrfToken } from './';
const ID_COOKIE_NAME = "sIdRefreshToken";

@@ -16,3 +17,3 @@ /**

*/
export function onUnauthorisedResponse(REFRESH_TOKEN_URL, preRequestIdToken) {
export function onUnauthorisedResponse(refreshTokenUrl, preRequestIdToken) {
return __awaiter(this, void 0, void 0, function* () {

@@ -31,3 +32,3 @@ let lock = new Lock();

}
let response = yield fetch(REFRESH_TOKEN_URL, {
let response = yield fetch(refreshTokenUrl, {
method: "post"

@@ -41,3 +42,8 @@ });

}
return { result: "SESSION_REFRESHED", apiResponse: response };
response.headers.forEach((value, key) => {
if (key.toString() === "anti-csrf") {
AntiCsrfToken.setItem(getIDFromCookie(), value);
}
});
return { result: "RETRY" };
}

@@ -44,0 +50,0 @@ catch (error) {

import Lock from 'browser-tabs-lock';
import { AntiCsrfToken } from './';
const ID_COOKIE_NAME = "sIdRefreshToken"

@@ -9,5 +11,4 @@

*/
export async function onUnauthorisedResponse(REFRESH_TOKEN_URL: string, preRequestIdToken: string):
export async function onUnauthorisedResponse(refreshTokenUrl: string, preRequestIdToken: string):
Promise<{ result: "SESSION_EXPIRED" } |
{ result: "SESSION_REFRESHED", apiResponse: any } |
{ result: "API_ERROR", error: any } |

@@ -27,3 +28,3 @@ { result: "RETRY" }> {

}
let response = await fetch(REFRESH_TOKEN_URL, {
let response = await fetch(refreshTokenUrl, {
method: "post"

@@ -37,3 +38,8 @@ });

}
return { result: "SESSION_REFRESHED", apiResponse: response };
response.headers.forEach((value, key) => {
if (key.toString() === "anti-csrf") {
AntiCsrfToken.setItem(getIDFromCookie(), value);
}
});
return { result: "RETRY" };
} catch (error) {

@@ -40,0 +46,0 @@ if (getIDFromCookie() === undefined) { // removed by server.

@@ -10,2 +10,40 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {

import { getIDFromCookie, onUnauthorisedResponse } from './handleSessionExp';
export class AntiCsrfToken {
constructor() { }
static getToken(associatedIdRefreshToken) {
if (associatedIdRefreshToken === undefined) {
AntiCsrfToken.tokenInfo = undefined;
return undefined;
}
if (AntiCsrfToken.tokenInfo === undefined) {
let antiCsrf = window.localStorage.getItem("anti-csrf-localstorage");
if (antiCsrf === null) {
return undefined;
}
AntiCsrfToken.tokenInfo = {
antiCsrf, associatedIdRefreshToken
};
}
else if (AntiCsrfToken.tokenInfo.associatedIdRefreshToken !== associatedIdRefreshToken) {
// csrf token has changed.
AntiCsrfToken.tokenInfo = undefined;
return AntiCsrfToken.getToken(associatedIdRefreshToken);
}
return AntiCsrfToken.tokenInfo.antiCsrf;
}
static removeToken() {
AntiCsrfToken.tokenInfo = undefined;
window.localStorage.removeItem("anti-csrf-localstorage");
}
static setItem(associatedIdRefreshToken, antiCsrf) {
if (associatedIdRefreshToken === undefined) {
AntiCsrfToken.tokenInfo = undefined;
return undefined;
}
window.localStorage.setItem("anti-csrf-localstorage", antiCsrf);
AntiCsrfToken.tokenInfo = {
antiCsrf, associatedIdRefreshToken
};
}
}
/**

@@ -37,8 +75,12 @@ * @description returns true if retry, else false is session has expired completely.

export default class AuthHttpRequest {
static init(REFRESH_TOKEN_URL, UNAUTHORISED_STATUS_CODE) {
AuthHttpRequest.REFRESH_TOKEN_URL = REFRESH_TOKEN_URL;
AuthHttpRequest.UNAUTHORISED_STATUS_CODE = UNAUTHORISED_STATUS_CODE;
static init(refreshTokenUrl, sessionExpiredStatusCode) {
AuthHttpRequest.refreshTokenUrl = refreshTokenUrl;
if (sessionExpiredStatusCode !== undefined) {
AuthHttpRequest.sessionExpiredStatusCode = sessionExpiredStatusCode;
}
AuthHttpRequest.initCalled = true;
}
}
AuthHttpRequest.UNAUTHORISED_STATUS_CODE = 440;
AuthHttpRequest.sessionExpiredStatusCode = 440;
AuthHttpRequest.initCalled = false;
/**

@@ -50,43 +92,65 @@ * @description sends the actual http request and returns a response if successful/

*/
AuthHttpRequest.doRequest = (httpCall) => __awaiter(this, void 0, void 0, function* () {
let throwError = false;
let returnObj = undefined;
while (true) {
// we read this here so that if there is a session expiry error, then we can compare this value (that caused the error) with the value after the request is sent.
// to avoid race conditions
const preRequestIdToken = getIDFromCookie();
try {
let response = yield httpCall();
if (response.status === AuthHttpRequest.UNAUTHORISED_STATUS_CODE) {
let retry = yield handleUnauthorised(AuthHttpRequest.REFRESH_TOKEN_URL, preRequestIdToken);
if (!retry) {
returnObj = response;
break;
AuthHttpRequest.doRequest = (httpCall, config) => __awaiter(this, void 0, void 0, function* () {
if (!AuthHttpRequest.initCalled) {
throw Error("init function not called");
}
try {
let throwError = false;
let returnObj = undefined;
while (true) {
// we read this here so that if there is a session expiry error, then we can compare this value (that caused the error) with the value after the request is sent.
// to avoid race conditions
const preRequestIdToken = getIDFromCookie();
const antiCsrfToken = AntiCsrfToken.getToken(preRequestIdToken);
let configWithAntiCsrf = config;
if (antiCsrfToken !== undefined) {
configWithAntiCsrf = Object.assign({}, configWithAntiCsrf, { headers: configWithAntiCsrf === undefined ? {
"anti-csrf": antiCsrfToken
} : Object.assign({}, configWithAntiCsrf.headers, { "anti-csrf": antiCsrfToken }) });
}
try {
let response = yield httpCall(configWithAntiCsrf);
if (response.status === AuthHttpRequest.sessionExpiredStatusCode) {
let retry = yield handleUnauthorised(AuthHttpRequest.refreshTokenUrl, preRequestIdToken);
if (!retry) {
returnObj = response;
break;
}
}
else {
response.headers.forEach((value, key) => {
if (key.toString() === "anti-csrf") {
AntiCsrfToken.setItem(getIDFromCookie(), value);
}
});
return response;
}
}
else {
return response;
}
}
catch (err) {
if (err.status === AuthHttpRequest.UNAUTHORISED_STATUS_CODE) {
let retry = yield handleUnauthorised(AuthHttpRequest.REFRESH_TOKEN_URL, preRequestIdToken);
if (!retry) {
throwError = true;
returnObj = err;
break;
catch (err) {
if (err.status === AuthHttpRequest.sessionExpiredStatusCode) {
let retry = yield handleUnauthorised(AuthHttpRequest.refreshTokenUrl, preRequestIdToken);
if (!retry) {
throwError = true;
returnObj = err;
break;
}
}
else {
throw err;
}
}
else {
throw err;
}
}
// if it comes here, means we breaked. which happens only if we have logged out.
if (throwError) {
throw returnObj;
}
else {
return returnObj;
}
}
// if it comes here, means we breaked. which happens only if we have logged out.
if (throwError) {
throw returnObj;
finally {
if (getIDFromCookie() === undefined) {
AntiCsrfToken.removeToken();
}
}
else {
return returnObj;
}
});

@@ -99,24 +163,34 @@ /**

AuthHttpRequest.attemptRefreshingSession = () => __awaiter(this, void 0, void 0, function* () {
const preRequestIdToken = getIDFromCookie();
return yield handleUnauthorised(AuthHttpRequest.REFRESH_TOKEN_URL, preRequestIdToken);
if (!AuthHttpRequest.initCalled) {
throw Error("init function not called");
}
try {
const preRequestIdToken = getIDFromCookie();
return yield handleUnauthorised(AuthHttpRequest.refreshTokenUrl, preRequestIdToken);
}
finally {
if (getIDFromCookie() === undefined) {
AntiCsrfToken.removeToken();
}
}
});
AuthHttpRequest.get = (url, config) => __awaiter(this, void 0, void 0, function* () {
return yield AuthHttpRequest.doRequest(() => {
return yield AuthHttpRequest.doRequest((config) => {
return fetch(url, Object.assign({ method: "GET" }, config));
});
}, config);
});
AuthHttpRequest.post = (url, config) => __awaiter(this, void 0, void 0, function* () {
return yield AuthHttpRequest.doRequest(() => {
return yield AuthHttpRequest.doRequest((config) => {
return fetch(url, Object.assign({ method: "POST" }, config));
});
}, config);
});
AuthHttpRequest.delete = (url, config) => __awaiter(this, void 0, void 0, function* () {
return yield AuthHttpRequest.doRequest(() => {
return yield AuthHttpRequest.doRequest((config) => {
return fetch(url, Object.assign({ method: "DELETE" }, config));
});
}, config);
});
AuthHttpRequest.put = (url, config) => __awaiter(this, void 0, void 0, function* () {
return yield AuthHttpRequest.doRequest(() => {
return yield AuthHttpRequest.doRequest((config) => {
return fetch(url, Object.assign({ method: "PUT" }, config));
});
}, config);
});
import { getIDFromCookie, onUnauthorisedResponse } from './handleSessionExp';
export class AntiCsrfToken {
private static tokenInfo: undefined | {
antiCsrf: string, associatedIdRefreshToken: string
};
private constructor() { }
static getToken(associatedIdRefreshToken: string | undefined): string | undefined {
if (associatedIdRefreshToken === undefined) {
AntiCsrfToken.tokenInfo = undefined;
return undefined;
}
if (AntiCsrfToken.tokenInfo === undefined) {
let antiCsrf = window.localStorage.getItem("anti-csrf-localstorage");
if (antiCsrf === null) {
return undefined;
}
AntiCsrfToken.tokenInfo = {
antiCsrf, associatedIdRefreshToken
};
} else if (AntiCsrfToken.tokenInfo.associatedIdRefreshToken !== associatedIdRefreshToken) {
// csrf token has changed.
AntiCsrfToken.tokenInfo = undefined;
return AntiCsrfToken.getToken(associatedIdRefreshToken);
}
return AntiCsrfToken.tokenInfo.antiCsrf;
}
static removeToken() {
AntiCsrfToken.tokenInfo = undefined;
window.localStorage.removeItem("anti-csrf-localstorage");
}
static setItem(associatedIdRefreshToken: string | undefined, antiCsrf: string) {
if (associatedIdRefreshToken === undefined) {
AntiCsrfToken.tokenInfo = undefined;
return undefined;
}
window.localStorage.setItem("anti-csrf-localstorage", antiCsrf);
AntiCsrfToken.tokenInfo = {
antiCsrf, associatedIdRefreshToken
};
}
}
/**

@@ -29,8 +74,12 @@ * @description returns true if retry, else false is session has expired completely.

private static REFRESH_TOKEN_URL: string | undefined;
private static UNAUTHORISED_STATUS_CODE = 440;
private static refreshTokenUrl: string | undefined;
private static sessionExpiredStatusCode = 440;
private static initCalled = false;
static init(REFRESH_TOKEN_URL: string, UNAUTHORISED_STATUS_CODE: number) {
AuthHttpRequest.REFRESH_TOKEN_URL = REFRESH_TOKEN_URL;
AuthHttpRequest.UNAUTHORISED_STATUS_CODE = UNAUTHORISED_STATUS_CODE;
static init(refreshTokenUrl: string, sessionExpiredStatusCode?: number) {
AuthHttpRequest.refreshTokenUrl = refreshTokenUrl;
if (sessionExpiredStatusCode !== undefined) {
AuthHttpRequest.sessionExpiredStatusCode = sessionExpiredStatusCode;
}
AuthHttpRequest.initCalled = true;
}

@@ -44,39 +93,66 @@

*/
static doRequest = async (httpCall: () => Promise<Response>): Promise<Response> => {
let throwError = false;
let returnObj = undefined;
while (true) {
// we read this here so that if there is a session expiry error, then we can compare this value (that caused the error) with the value after the request is sent.
// to avoid race conditions
const preRequestIdToken = getIDFromCookie();
try {
let response = await httpCall();
if (response.status === AuthHttpRequest.UNAUTHORISED_STATUS_CODE) {
let retry = await handleUnauthorised(AuthHttpRequest.REFRESH_TOKEN_URL, preRequestIdToken);
if (!retry) {
returnObj = response;
break;
}
} else {
return response;
static doRequest = async (httpCall: (config?: RequestInit) => Promise<Response>, config?: RequestInit): Promise<Response> => {
if (!AuthHttpRequest.initCalled) {
throw Error("init function not called");
}
try {
let throwError = false;
let returnObj = undefined;
while (true) {
// we read this here so that if there is a session expiry error, then we can compare this value (that caused the error) with the value after the request is sent.
// to avoid race conditions
const preRequestIdToken = getIDFromCookie();
const antiCsrfToken = AntiCsrfToken.getToken(preRequestIdToken);
let configWithAntiCsrf: RequestInit | undefined = config;
if (antiCsrfToken !== undefined) {
configWithAntiCsrf = {
...configWithAntiCsrf,
headers: configWithAntiCsrf === undefined ? {
"anti-csrf": antiCsrfToken
} : {
...configWithAntiCsrf.headers,
"anti-csrf": antiCsrfToken
}
};
}
} catch (err) {
if (err.status === AuthHttpRequest.UNAUTHORISED_STATUS_CODE) {
let retry = await handleUnauthorised(AuthHttpRequest.REFRESH_TOKEN_URL, preRequestIdToken);
if (!retry) {
throwError = true;
returnObj = err;
break;
try {
let response = await httpCall(configWithAntiCsrf);
if (response.status === AuthHttpRequest.sessionExpiredStatusCode) {
let retry = await handleUnauthorised(AuthHttpRequest.refreshTokenUrl, preRequestIdToken);
if (!retry) {
returnObj = response;
break;
}
} else {
response.headers.forEach((value, key) => {
if (key.toString() === "anti-csrf") {
AntiCsrfToken.setItem(getIDFromCookie(), value);
}
});
return response;
}
} else {
throw err;
} catch (err) {
if (err.status === AuthHttpRequest.sessionExpiredStatusCode) {
let retry = await handleUnauthorised(AuthHttpRequest.refreshTokenUrl, preRequestIdToken);
if (!retry) {
throwError = true;
returnObj = err;
break;
}
} else {
throw err;
}
}
}
// if it comes here, means we breaked. which happens only if we have logged out.
if (throwError) {
throw returnObj;
} else {
return returnObj;
}
} finally {
if (getIDFromCookie() === undefined) {
AntiCsrfToken.removeToken();
}
}
// if it comes here, means we breaked. which happens only if we have logged out.
if (throwError) {
throw returnObj;
} else {
return returnObj;
}
}

@@ -90,8 +166,17 @@

static attemptRefreshingSession = async (): Promise<boolean> => {
const preRequestIdToken = getIDFromCookie();
return await handleUnauthorised(AuthHttpRequest.REFRESH_TOKEN_URL, preRequestIdToken);
if (!AuthHttpRequest.initCalled) {
throw Error("init function not called");
}
try {
const preRequestIdToken = getIDFromCookie();
return await handleUnauthorised(AuthHttpRequest.refreshTokenUrl, preRequestIdToken);
} finally {
if (getIDFromCookie() === undefined) {
AntiCsrfToken.removeToken();
}
}
}
static get = async (url: RequestInfo, config?: RequestInit) => {
return await AuthHttpRequest.doRequest(() => {
return await AuthHttpRequest.doRequest((config?: RequestInit) => {
return fetch(url, {

@@ -101,7 +186,7 @@ method: "GET",

});
});
}, config);
}
static post = async (url: RequestInfo, config?: RequestInit) => {
return await AuthHttpRequest.doRequest(() => {
return await AuthHttpRequest.doRequest((config?: RequestInit) => {
return fetch(url, {

@@ -111,7 +196,7 @@ method: "POST",

});
});
}, config);
}
static delete = async (url: RequestInfo, config?: RequestInit) => {
return await AuthHttpRequest.doRequest(() => {
return await AuthHttpRequest.doRequest((config?: RequestInit) => {
return fetch(url, {

@@ -121,7 +206,7 @@ method: "DELETE",

});
});
}, config);
}
static put = async (url: RequestInfo, config?: RequestInit) => {
return await AuthHttpRequest.doRequest(() => {
return await AuthHttpRequest.doRequest((config?: RequestInit) => {
return fetch(url, {

@@ -131,4 +216,4 @@ method: "PUT",

});
});
}, config);
}
}
{
"name": "supertokens-website",
"version": "2.0.1",
"version": "3.0.0",
"description": "frontend sdk for website to be used for auth solution.",

@@ -9,5 +9,15 @@ "main": "index.js",

},
"devDependencies": {},
"devDependencies": {
"cookie-parser": "^1.4.4",
"express": "4.17.1",
"isomorphic-fetch": "^2.2.1",
"mocha": "6.1.4",
"mocha-jsdom": "2.0.0",
"supertest": "4.0.2",
"supertokens-node-mysql-ref-jwt": "git+https://github.com/supertokens/supertokens-node-mysql-ref-jwt.git#dev",
"typescript": "3.5.2"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "TEST_MODE=testing npx mocha",
"build": "npx tsc -p tsconfig.json"
},

@@ -37,2 +47,2 @@ "repository": {

"homepage": "https://github.com/supertokens/supertokens-website#readme"
}
}

@@ -5,140 +5,5 @@ ![SuperTokens banner](https://raw.githubusercontent.com/supertokens/supertokens-logo/master/images/Artboard%20%E2%80%93%2027%402x.png)

This library implements the **frontend part of user session management for websites**. You can use this to make http(s) API calls to your backend that require an authenticated user.
## Documentation
Please visit [our website](https://supertokens.io) to see which tech stack you can use this with. One you find the right library, you will find the documentation for how to use this package along with it. For example, please [see this](https://supertokens.github.io/supertokens-node-mysql-ref-jwt/docs/frontend/website/installation) for how to use this library along with our NodeJS and MySQL solution.
Features:
- When you make an API call, and if the access token has expired, this library will automatically take care of refreshing the token for you. After successfully refreshing it, it will call your API with the new token again and return its response.
- Takes care of race conditions mentioned in the footer of [this blog post](https://hackernoon.com/the-best-way-to-securely-manage-user-sessions-91f27eeef460).
## Index
1) [Installation](https://github.com/supertokens/supertokens-website#installation)
2) [API](https://github.com/supertokens/supertokens-website#api)
3) [Usage with GraphQL - apollo-client & apollo-link-http](https://github.com/supertokens/supertokens-website#usage-with-graphql---apollo-client--apollo-link-http)
4) [Example code & Demo](https://github.com/supertokens/supertokens-website#example-code--demo)
5) [Making changes](https://github.com/supertokens/supertokens-website#making-changes)
6) [Support, questions and bugs](https://github.com/supertokens/supertokens-website#support-questions-and-bugs)
7) [Authors](https://github.com/supertokens/supertokens-website#authors)
## Installation
To get started, you just need to do:
```bash
npm i --save supertokens-website
```
## API
This library is to be used instead of fetch in places where the API requires authentication.
```js
import * as SuperTokensRequest from "supertokens-website";
```
### SuperTokensRequest.init(refreshTokenURL, sessionExpiredStatusCode)
- To be called at least once before any http request is made from your frontend that uses this library. For example, if your website is a single page ReactJS app, then you can call this in the constructor of the root component.
```js
// @params refreshTokenURL: this is the path to API endpoint that is responsible for refreshing the session when the access token expires.
// @params sessionExpiredStatusCode: this is the status code that will be sent by any API that detects session expiry.
// @returns void
SuperTokensRequest.init("/api/refreshtoken", 440)
```
### SuperTokensRequest.get(url, config)
- send a GET request to this url - to be used only with your app's APIs
```js
// @params url: endpoint to your GET API
// @params config: this is same as fetch config
// @returns Promise
SuperTokensRequest.get("/someAPI", config).then(response => {
// API response.
}).catch(err => {
// handle error
});
```
### SuperTokensRequest.post(url, config)
- send a POST request to this url - to be used only with your app's APIs
```js
// @params url: endpoint to your POST API
// @params config: this is same as fetch config
// @returns Promise
SuperTokensRequest.post("/someAPI", config).then(response => {
// API response.
}).catch(err => {
// handle error
});
```
### SuperTokensRequest.delete(url, config)
- send a DELETE request to this url - to be used only with your app's APIs
```js
// @params url: endpoint to your DELETE API
// @params config: this is same as fetch config
// @returns Promise
SuperTokensRequest.delete("/someAPI", config).then(response => {
// API response.
}).catch(err => {
// handle error
});
```
### SuperTokensRequest.put(url, config)
- send a PUT request to this url - to be used only with your app's APIs
```js
// @params url: endpoint to your PUT API
// @params config: this is same as fetch config
// @returns Promise
SuperTokensRequest.put("/someAPI", config).then(response => {
// API response.
}).catch(err => {
// handle error
});
```
### SuperTokensRequest.doRequest(func)
- use this function to send a request using any other http method that is not mentioned above
```js
// @params func: a function that returns a Promise returned by calling the fetch function
// @returns Promise
SuperTokensRequest.doRequest(() => fetch(...)).then(response => {
// API response.
}).catch(err => {
// handle error
});
```
### SuperTokensRequest.attemptRefreshingSession()
- use this function when you want to manually refresh the session.
```js
// @params func: a function that returns a Promise returned by calling the fetch function
// @returns Promise
SuperTokensRequest.attemptRefreshingSession().then(success => {
if (success) {
// session may have refreshed successfully
} else {
// user has been logged out. Redirect to login page
}
}).catch(err => {
// handle error
});
```
## Usage with GraphQL - apollo-client & apollo-link-http
First, we init the SuperTokens library
```js
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import SuperTokensRequest from 'supertokens-website';
SuperTokensRequest.init("/api/refreshtoken", 440) // /api/refreshtoken is just an example
```
Then we create the ```ApolloClient``` as follows
```js
const client = new ApolloClient({
link: new HttpLink({
uri: "/graphql", // change this depending on your path
fetch: (uri, options) => {
return SuperTokensRequest.doRequest(() => {
return fetch(uri, options);
});
}
}),
cache: new InMemoryCache(), // change this depending on your preference
// ... other params
});
```
## Example code & Demo
You can play around with the [demo project](https://github.com/supertokens/auth-demo) that uses this and the [supertokens-node-mysql-ref-jwt](https://github.com/supertokens/supertokens-node-mysql-ref-jwt) library. The demo demonstrates how this package behaves when it detects auth token theft (and the best part - you are the hacker here, muahahaha!)
## Making changes

@@ -145,0 +10,0 @@ This library is written in TypeScript (TS). When you make any changes to the .ts files in the root folder, run the following command to compile to .js:

@@ -8,4 +8,3 @@ {

"noImplicitThis": true,
"jsx": "react",
"module": "ES6",
"module": "es6",
"moduleResolution": "node"

@@ -12,0 +11,0 @@ },

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