diff --git a/config.js b/config.js index 4472af26e..4a3609dc8 100644 --- a/config.js +++ b/config.js @@ -7,6 +7,8 @@ // entrypoint for the entire applications configuration. require('env-rewrite').rewrite(); +const uniq = require('lodash/uniq'); + //============================================================================== // CONFIG INITIALIZATION //============================================================================== @@ -31,6 +33,13 @@ const CONFIG = { // token. JWT_COOKIE_NAME: process.env.TALK_JWT_COOKIE_NAME || 'authorization', + // JWT_SIGNING_COOKIE_NAME will be the cookie set when cookies are issued. + // This defaults to the TALK_JWT_COOKIE_NAME value. + JWT_SIGNING_COOKIE_NAME: process.env.TALK_JWT_SIGNING_COOKIE_NAME || process.env.TALK_JWT_COOKIE_NAME || 'authorization', + + // JWT_COOKIE_NAMES declares the many cookie names used for verification. + JWT_COOKIE_NAMES: process.env.TALK_JWT_COOKIE_NAMES || null, + // JWT_CLEAR_COOKIE_LOGOUT specifies whether the named cookie should be // cleared when the user is logged out. JWT_CLEAR_COOKIE_LOGOUT: process.env.TALK_JWT_CLEAR_COOKIE_LOGOUT ? process.env.TALK_JWT_CLEAR_COOKIE_LOGOUT !== 'FALSE' : true, @@ -165,6 +174,16 @@ if (CONFIG.JWT_DISABLE_ISSUER) { CONFIG.JWT_ISSUER = undefined; } +// Parse and handle cookie names. +if (CONFIG.JWT_COOKIE_NAMES) { + CONFIG.JWT_COOKIE_NAMES = CONFIG.JWT_COOKIE_NAMES.split(','); +} else { + CONFIG.JWT_COOKIE_NAMES = []; +} + +// Add in the default cookie names and strip duplicates. +CONFIG.JWT_COOKIE_NAMES = uniq(CONFIG.JWT_COOKIE_NAMES.concat([CONFIG.JWT_COOKIE_NAME, CONFIG.JWT_SIGNING_COOKIE_NAME])); + //------------------------------------------------------------------------------ // External database url's //------------------------------------------------------------------------------ diff --git a/docs/_docs/02-01-configuration.md b/docs/_docs/02-01-configuration.md index 74bd8972e..18a483723 100644 --- a/docs/_docs/02-01-configuration.md +++ b/docs/_docs/02-01-configuration.md @@ -87,8 +87,16 @@ on the contents of those variables.** These are advanced settings for fine tuning the auth integration, and is not needed in most situations. -- `TALK_JWT_COOKIE_NAME` (_optional_) - the name of the cookie to extract the - JWT from (Default `authorization`) +- `TALK_JWT_COOKIE_NAME` (_optional_) - the default cookie name to check for a + valid JWT token to use for verifying a user. (Default `authorization`) +- `TALK_JWT_SIGNING_COOKIE_NAME` (_optional_) - the default cookie name that is + use to set a cookie containing a JWT that was issued by Talk. + (Default `process.env.TALK_JWT_COOKIE_NAME`) +- `TALK_JWT_COOKIE_NAMES` (_optional_) - the different cookie names to check for + a JWT token in, seperated by `,`. By default, we always use the + `process.env.TALK_JWT_COOKIE_NAME` and `process.env.TALK_JWT_SIGNING_COOKIE_NAME` + for this value. Any additional cookie names specified here will be appended to + the list of cookie names to inspect. - `TALK_JWT_CLEAR_COOKIE_LOGOUT` (_optional_) - when `FALSE`, Talk will not clear the cookie with name `TALK_JWT_COOKIE_NAME` when logging out (Default `TRUE`) @@ -115,6 +123,14 @@ will be used: } ``` +When our passport middleware checks for JWT tokens, it searches in the following +order: + +1. Custom cookies named from the list in `TALK_JWT_COOKIE_NAMES`. +2. Default cookies named `TALK_JWT_COOKIE_NAME` then `TALK_JWT_SIGNING_COOKIE_NAME`. +3. Query parameter `?access_token={TOKEN}`. +4. Header: `Authorization: Bearer {TOKEN}`. + ### Email - `TALK_SMTP_EMAIL` (*required for email*) - the address to send emails from diff --git a/services/passport.js b/services/passport.js index 409bb2279..14a2134ce 100644 --- a/services/passport.js +++ b/services/passport.js @@ -23,7 +23,8 @@ const { JWT_ALG, RECAPTCHA_SECRET, RECAPTCHA_ENABLED, - JWT_COOKIE_NAME, + JWT_SIGNING_COOKIE_NAME, + JWT_COOKIE_NAMES, JWT_CLEAR_COOKIE_LOGOUT, JWT_USER_ID_CLAIM, } = require('../config'); @@ -53,7 +54,7 @@ const GenerateToken = (user) => { const SetTokenForSafari = (req, res, token) => { const browser = bowser._detect(req.headers['user-agent']); if (browser.ios || browser.safari) { - res.cookie(JWT_COOKIE_NAME, token, { + res.cookie(JWT_SIGNING_COOKIE_NAME, token, { httpOnly: true, secure: process.env.NODE_ENV === 'production', expires: new Date(Date.now() + ms(JWT_EXPIRY)) @@ -169,7 +170,7 @@ const HandleLogout = (req, res, next) => { // Only clear the cookie on logout if enabled. if (JWT_CLEAR_COOKIE_LOGOUT) { - res.clearCookie(JWT_COOKIE_NAME); + res.clearCookie(JWT_SIGNING_COOKIE_NAME); } res.status(204).end(); @@ -209,14 +210,20 @@ const CheckBlacklisted = async (jwt) => { const JwtStrategy = require('passport-jwt').Strategy; const ExtractJwt = require('passport-jwt').ExtractJwt; -let cookieExtractor = function(req) { - let token = null; - +let cookieExtractor = (req) => { if (req && req.cookies) { - token = req.cookies[JWT_COOKIE_NAME]; + + // Walk over all the cookie names in JWT_COOKIE_NAMES. + for (const cookieName of JWT_COOKIE_NAMES) { + + // Check to see if that cookie is set. + if (cookieName in req.cookies && req.cookies[cookieName] !== null && req.cookies[cookieName].length > 0) { + return req.cookies[cookieName]; + } + } } - return token; + return null; }; // Override the JwtVerifier method on the JwtStrategy so we can pack the