Package Exports
- supertokens-node-mysql-ref-jwt
- supertokens-node-mysql-ref-jwt/indexRaw
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (supertokens-node-mysql-ref-jwt) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
This library implements user session management for websites that run on NodeJS, Express and MySQL. This is meant to be used with your backend code. For a complete solution, you also need to use the supertokens-website package on your frontend. A demo project that uses these two libraries is available here: auth-demo
The protocol SuperTokens uses is described in detail in this article
The library has the following features:
- It uses short-lived access tokens (JWT) and long-lived refresh tokens (Opaque).
- Minimises probability of token theft - follows all the best practices for handling auth tokens across all attack surfaces: the frontend, backend and during transit
- Token theft detection: SuperTokens is able to detect token theft in a robust manner. Please see the article mentioned above for details on how this works.
- Complete auth token management - It only stores the hashed version of refresh tokens in the database, so even if someone (an attacker or an employee) gets access to the table containing them, they would not be able to hijack any session. Furthermore, the tokens sent over to the client have a long length and high entropy - so brute force attack is out of the question.
- Automatic JWT signing key generation (if you don't provide one), management and rotation - Periodic changing of this key enables maximum security as you don't have to worry much in the event that this key is compromised. Also note that doing this change will not log any user out π
- Complete cookie management - Takes care of making them secure and HttpOnly. Also removes, adds and edits them whenever needed. You do not have to worry about cookies and its security anymore!
- Efficient in terms of space complexity - Needs to store just one row in a SQL table per logged in user per device.
- Efficient in terms of time complexity - Minimises the number of DB lookups (most requests do not need a database call to authenticate at all!)
- Built-in support for handling multiple devices per user.
- Built-in synchronisation in case you are running multiple node processes.
- Easy to use (see auth-demo), with well documented, modularised code and helpful error messages!
- Using this library, you can keep a user logged in for however long you want - without worrying about any security consequences.
If you like this project and want to use it, but for a different tech stack:
- Please contact us at team@supertokens.io and we will evaluate building a solution for your tech stack. This is on a first come, first serve basis.
- We will soon be making this into a standalone process as well. So you could then use this package with any tech stack.
Index
- Installation
- Accompanying library
- Usage
- Example code & Demo
- Making changes
- Future work
- Support, questions and bugs
- Further reading and understanding
- Authors
Installation
npm i --save supertokens-node-mysql-ref-jwt
Before you start using the package: You will need to create a database in MySQL to store session info. This database can be either your already created DB or a new DB. This database name should be given as a config param to the library (See config section below).
There will be two tables created automatically for you in the provided database when you first use this library - if they don't already exist. If you want to create them yourself, you can do so with the following commands:
CREATE TABLE signing_key (
key_name VARCHAR(128),
key_value VARCHAR(255),
created_at_time BIGINT UNSIGNED,
PRIMARY KEY(key_name)
);
CREATE TABLE refresh_tokens (
session_handle_hash_1 VARCHAR(255) NOT NULL,
user_id VARCHAR(128) NOT NULL,
refresh_token_hash_2 VARCHAR(128) NOT NULL,
session_info TEXT,
expires_at BIGINT UNSIGNED NOT NULL,
jwt_user_payload TEXT,
PRIMARY KEY(session_handle_hash_1)
);
You can name these tables whatever you want, but be sure to send those to the library via the config params (see below).
You will also need to use the cookie-parser npm module as a middleware for this library to work:
import * as cookieParser from 'cookie-parser';
app.use(cookieParser());
Accompanying library
As of now, this library will only work if your frontend is a website. To use this library, you will need to use the supertokens-website in your frontend code. This library is a drop-in replacement for your axios/ajax calls on the frontend.
Together this library and the supertokens-website library take into account all the failures and race conditions that can possibly occur when implementing session management.
Usage
SuperTokens
import * as SuperTokens from 'supertokens-node-mysql-ref-jwt';
SuperTokens.init(config)
- To be called while starting your server
// @params config: An object which allows you to set the behaviour of this library. See the Config section below for details on the options.
// @returns a Promise
SuperTokens.init(config).then(() => {
// Success! Your app can now use this library in any API
}).catch(err => {
// type of err is SuperTokens.Error Will be GENERAL_ERROR
});
SuperTokens.createNewSession(res, userId, jwtPayload?, sessionData?)
- To be called when you want to login a user, after verifying their credentials.
// @params res: express response object
// @params userId: string - some unique ID for this user for you to retrieve in your APIs
// @params: jwtPayload - default is undefined. any js object/array/primitive type to store in the JWT's payload. Once set, it cannot be changed for this session. Also, this should not contain any sensitive data. The advantage of this is that for any API call, you do not need a database lookup to retrieve the information stored here.
// @params: sessionData - default is undefined. any js object/array/primitive type to store in the DB for this session. This is changeable throughout the lifetime of this session
// @returns a Promise
SuperTokens.createNewSession(res, "User1", {info: "Data in JWT"}, {info: "Data stored in DB"}).then(session => {
// session is of type Session class - See below.
}).catch(err => {
// type of err is SuperTokens.Error Will be GENERAL_ERROR
});
SuperTokens.getSession(req, res)
- To be called in any API that requires an authenticated user.
// @params req: express request object
// @params res: express response object
// @returns a Promise
SuperTokens.getSession(req, res).then(session => {
// session is of type Session class.
}).catch(err => {
// type of err is SuperTokens.Error Will be GENERAL_ERROR, UNAUTHORISED or TRY_REFRESH_TOKEN
// most of the time, if err is not GENERAL_ERROR, then you should respond with a status code that indicates session expiry. For more details, please see the SuperTokens.Error section below.
});
SuperTokens.refreshSession(req, res)
- To be called only in the API that is responsible for refreshing your access token. Calls to this API should be handled by the supertokens-website package.
// @params req: express request object
// @params res: express response object
// @returns a Promise
SuperTokens.refreshSession(req, res).then(session => {
// session is of type Session class.
}).catch(err => {
// type of err is SuperTokens.Error Will be GENERAL_ERROR, UNAUTHORISED
// if err is not GENERAL_ERROR, then you should respond with a status code that indicates session expiry.
});
SuperTokens.revokeAllSessionsForUser(userId)
- To be called when you want this user to be logged out of all devices. Note that this will not cause immediate log out for this user. The actual time they would be logged out is when their access token expires, and since these are short-lived, that should be soon after calling this function.
// @params userId: string - a unique ID identifying this user. This ID should be the same as what was passed when calling SuperTokens.createNewSession
// @returns a Promise
SuperTokens.revokeAllSessionsForUser("User1").then(() => {
// success
}).catch(err => {
// type of err is SuperTokens.Error Will be GENERAL_ERROR
});
SuperTokens.revokeSessionUsingSessionHandle(sessionHandle)
- To be called when the token theft callback is called (see configs). The callback function will give you a sessionHandle and a userId. Using the sessionHandle, you can logout any device that is using that particular session. This enables you to keep other devices of this userId still logged in.
// @params sessionHandle: string - a unique ID identifying this session.
// @returns a Promise
SuperTokens.revokeSessionUsingSessionHandle(sessionHandle).then(() => {
// success
}).catch(err => {
// type of err is SuperTokens.Error Will be GENERAL_ERROR
});
Session
An instance of this class will be returned to you from some of the functions mentioned above.
session.getUserId()
- To be called when you want to get the unique ID of the user for whom this session was created
// @returns a string
let userId = session.getUserId()
session.getJWTPayload()
- To be called when you want to get the JWT payload that was set when creating the session
// @returns a js object/array/primitive type - depending on what you passed in createNewSession
let payloadInfo = session.getJWTPayload()
session.revokeSession()
- To be called when you want to logout a user.
// @returns a promise
session.revokeSession().then(() => {
// success. user has been logged out.
}).catch(err => {
// type of err is SuperTokens.Error Will be GENERAL_ERROR
});
session.getSessionData()
- To be called when you want to get the stored session data from DB.
- Note that this function does not do any sort of synchronisation with other processes that may want to get/update session data for this session.
// @returns a promise
session.getSessionData().then((data) => {
// success.
}).catch(err => {
// type of err is SuperTokens.Error Will be GENERAL_ERROR and UNAUTHORISED
});
session.updateSessionData()
- To be called when you want to update the stored (in DB) session data. This will overwrite any currently stored data for this session.
- Note that this function does not do any sort of synchronisation with other processes that may want to get/update session data for this session.
// @params a js object/array/primitive type
// @returns a promise
session.updateSessionData(data).then(() => {
// success.
}).catch(err => {
// type of err is SuperTokens.Error Will be GENERAL_ERROR and UNAUTHORISED
});
SuperTokens.Error
This is thrown in many of the functions that are mentioned above. There are of three types:
GENERAL_ERROR
// here the err will be the actual error generated by whatever caused the error.
// when this error is thrown, you would generally send a 500 status code to your client.
// example for when this is thrown is if your MySQL instance is down.
{errType: SuperTokens.Error.GENERAL_ERROR, err}
UNAUTHORISED
// here the err will be the actual error generated by whatever caused the error.
// when this error is thrown, you would generally consider this user logged out and would redirect them to a login page or send a session expiry status code
// example for when this is thrown is if the user clears their cookies or they have opened your app after a long time such that their refresh token has expired.
{errType: SuperTokens.Error.UNAUTHORISED, err}
TRY_REFRESH_TOKEN
// here the err will be the actual error generated by whatever caused the error.
// when this error is thrown, you would generally send a status code that would indicate session expiry.
// example for when this is thrown is if a user's access token has expired
// NOTE: this does not necessarily mean they are logged out! They could have a refresh token that may give them a new access token and then their session could continue.
{errType: SuperTokens.Error.TRY_REFRESH_TOKEN, err}
SuperTokens.Error.isErrorFromAuth(err)
// @params err: this is an error object thrown by anything
// @returns true if this error was generated by this SuperTokens library, else false.
// If you call this in TypeScript, then the err object will have a type {errType: number, err: any}
let isErrorGeneratedBySuperTokensLib = SuperTokens.Error.isErrorFromAuth(err)
In general
- In a GET API call which returns a rendered HTML page (for example when using server-side rendered ReactJS):
- If you get an UNAUTHORISED error, redirect to a login page.
- If you get a TRY_REFRESH_TOKEN error, then send HTML & JS that attempts to call the refreshtoken API via the supertokens-website package and if that is successful, call the current API again, else redirect to a login page.
- In all other APIs
- If you get an UNAUTHORISED or TRY_REFRESH_TOKEN error, send a status code that represents session expiry
Config
The config object has the following structure:
NOTE: If you do not provide a signingKey, it will create one for you and you can also configure it so that it changes after a fixed amount of time (for maximum security). The changing of the key will not log any user out.
config = {
mysql: {
host?: string, // default localhost
port?: number, // default 3306
user: string, // If the tables in the database are not created already, then this user must have permission to create tables.
password: string,
connectionLimit?: number, // default 50
database: string, // name of the database to connect to. This must be created before running this package
tables?: {
signingKey?: string, // default signing_key - table name used to store secret keys
refreshTokens?: string // default refresh_token - table name used to store sessions
}
},
tokens: {
accessToken?: {
signingKey?: {
dynamic?: boolean, // default true - if this is true, then the JWT signing key will change automatically ever updateInterval hours.
updateInterval?: number, // in hours - default 24 - should be >= 1 && <= 720. How often to change the signing key
get?: () => Promise<string> // default undefined - If you want to give your own JWT signing key, please give a function here. If this is given, then the dynamic boolean will be ignored as key management will be up to you. This function will be called everytime we generate or verify any JWT.
},
validity?: number // in seconds, default is 3600 seconds. should be >= 10 && <= 86400000 seconds. This determines the lifetime of an access token.
},
refreshToken: {
validity?: number, // in hours, default is 2400 (100 days). This determines how long a refresh token is alive for. So if your user is inactive for these many hours, they will be logged out.
renewTokenPath: string // this is the API path that needs to be called for refreshing a session. This needs to be a POST API. An example value is "/api/refreshtoken". This will also be the path of the refresh token cookie.
}
},
logging?: {
info?: (info: any) => void, // default undefined. This function, if provided, will be called for info logging purposes
error?: (err: any) => void // default undefined. This function, if provided, will be called for error logging purposes
},
cookie: {
domain: string, // this is the domain to set for all the cookies. The path for all cookies except the refresh token will be "/"
secure?: boolean // default true. Sets if the cookies are secure or not. Ideally, this value should be true in production mode.
},
onTokenTheftDetection?: (userId: string, sessionHandle: string) => void; // default undefined. This function is called when a refresh token theft is detected. The userId can be used to log out all devices that have this user signed in. Or the sessionHandle can be used to just log out this particular "stolen session".
}
To change the default values or ranges, please see /lib/ts/config.ts file.
Example code & Demo
You can play around with the demo project that uses this and the supertokens-website library. The demo demonstrates how this package behaves when it detects auth token theft (and the best part is that you are the attacker, muahahaha)!
Making changes
This library is written in TypeScript (TS). When you make any changes to the .ts files in the /lib/ts/* folder, run the following command in the /lib folder to compile to .js:
tsc -p tsconfig.json
If you make any changes to index.ts in the root of this repo, once you compile it to .js, remember to change the import/export path from /lib/ts/* to /lib/build/* in the .js file.
To change the name of the cookies used, please find them in /lib/ts/cookie.ts
Future work
- Enable this to work with mobile apps as well.
- Add unit testing.
- To implement info, debug and error logs in a better way.
- Making this a standalone process that has an http interface to it.
Support, questions and bugs
We are most accessible via team@supertokens.io and via the GitHub issues feature. We realise that our community is small at the moment and therefore we will actively provide support to anyone interested in this library.
General support includes the following (freely available from us forever):
- Fixing bugs and catering to issues that apply to most users of this library.
- Keeping docs and the code up to date.
- Answering questions that apply to most users via Stack Overflow, Email, Quora etc.
- Expanding the feature set of this library in a way that is helpful to most users.
- Catering to pull requests.
Dedicated support includes the following:
- Help in custom implementation of this library into your existing project / infrastructure.
- Implementation of custom flows or features for your session management needs.
- Consultation on your current session management system - help you improve it, identify and fix vulnerabilities, and suggest the best solution for you given your business requirements.
- Very high avaiability.
To show some love to our early adopters, weβre offering to give them free dedicated support until the 10th of July, 2019. Also, for every referral, we will give the referee and their referral an additional free month (upto 6 months) of dedicated support post July, 2019. A referral will be counted when a person/company does any of the following:
- Raises a reasonable and thoughtful issue.
- Contributes meaningfully to this project.
- Uses this repo in their project.
Referrals will be accepted only until this text is in the latest version of the README.md file
Further reading and understanding
We have written a blog post about sessions in general:
- Part 1: Introduction to session management, analysis of most commonly used session flows, and best practices
- Part 2: Analysis of the session flow used by SuperTokens.
To understand the logic behind how sessions are created, managed and destroyed, please refer to the WiKi section
Authors
Created with β€οΈ by the folks at SuperTokens. We are a startup passionate about security and solving software challenges in a way that's helpful for everyone! Please feel free to give us feedback at team@supertokens.io, until our website is ready π