New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

express-openid-connect

Package Overview
Dependencies
Maintainers
37
Versions
44
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

express-openid-connect - npm Package Compare versions

Comparing version 0.6.0 to 0.7.0

.circleci/config.yml

2

.eslintrc.json

@@ -14,3 +14,3 @@ {

2,
{ "SwitchCase": 1 }
{ "SwitchCase": 1 }
],

@@ -17,0 +17,0 @@ "linebreak-style": [

@@ -24,4 +24,4 @@ # Public API

- **`appSessionCookie`** - Object defining application session cookie attributes. Allowed keys are `domain`, `httpOnly`, `path`, `secure`, and `sameSite`. Defaults are `true` for `httpOnly` and `Lax` for `sameSite`.
- **`appSessionDuration`** - Integer value, in seconds, for application session duration. Set to `0` to indicate the cookie should be ephemeral (no expiration). Default is 7 days.
- **`appSessionCookie`** - Object defining application session cookie attributes. Allowed keys are `domain`, `ephemeral`, `httpOnly`, `path`, `secure`, and `sameSite`. Defaults are `true` for `httpOnly`, `Lax` for `sameSite`, and `false` for `ephemeral`. See the [Express Response documentation](https://expressjs.com/en/api.html#res.cookie) for more information on all properties except `ephemeral`.
- **`appSessionDuration`** - Integer value, in seconds, for application session duration. Default is 7 days.
- **`appSessionName`** - String value for the cookie name used for the internal session. This value must only include letters, numbers, and underscores. Default is `identity`.

@@ -41,2 +41,3 @@ - **`auth0Logout`** - Boolean value to enable Auth0's logout feature. Default is `false`.

- **`logoutPath`** - Relative path to application logout. Default is `/logout`.
- **`postLogoutRedirectUri`** - Either a relative path to the application or a valid URI to an external domain. The user will be redirected to this after a logout has been performed. Adding a `returnTo` parameter on the logout route will override this value. The value used must be registered at the authorization server. Default is `baseUrl`.
- **`redirectUriPath`** - Relative path to the application callback to process the response from the authorization server. This value is combined with the `baseUrl` and sent to the authorize endpoint as the `redirectUri` parameter. Default is `/callback`.

@@ -48,3 +49,3 @@ - **`required`** - Use a boolean value to require authentication for all routes. Pass a function instead to base this value on the request. Default is `true`.

The `authorizationParams` key defines the URL parameters used when redirecting users to the authorization server to log in. If this key is not provided by your application, its default value will be:
The `authorizationParams` key defines the URL parameters used when redirecting users to the authorization server to log in. If this key is not provided by your application, its default values will be:

@@ -59,3 +60,3 @@ ```js

A new object can be passed in to change what is returned from the authorization server depending on your specific scenario.
New values can be passed in to change what is returned from the authorization server depending on your specific scenario.

@@ -62,0 +63,0 @@ For example, to receive an access token for an API, you could initialize like the sample below. Note that `response_mode` can be omitted because the OAuth2 default mode of `query` is fine:

# CHANGELOG
## [v0.7.0](https://github.com/auth0/express-openid-connect/tree/v0.7.0) (2020-02-18)
[Full Changelog](https://github.com/auth0/express-openid-connect/compare/v0.6.0...v0.7.0)
**Added**
- Update TS defs for config functions [\#65](https://github.com/auth0/express-openid-connect/pull/65) ([joshcanhelp](https://github.com/joshcanhelp))
- Register Express as a peer dependency [\#63](https://github.com/auth0/express-openid-connect/pull/63) ([stevehobbsdev](https://github.com/stevehobbsdev))
- Add custom state handling [\#60](https://github.com/auth0/express-openid-connect/pull/60) ([joshcanhelp](https://github.com/joshcanhelp))
**Changed**
- Merge seperate config schemas [\#57](https://github.com/auth0/express-openid-connect/pull/57) ([joshcanhelp](https://github.com/joshcanhelp))
- Update hapi to v16 and fix breaking changes [\#56](https://github.com/auth0/express-openid-connect/pull/56) ([joshcanhelp](https://github.com/joshcanhelp))
- Update hapi/joi to 15.x; update other deps to minor/patch [\#51](https://github.com/auth0/express-openid-connect/pull/51) ([joshcanhelp](https://github.com/joshcanhelp))
**Fixed**
- Additional allowed cookieOptions [\#53](https://github.com/auth0/express-openid-connect/pull/53) ([joshcanhelp](https://github.com/joshcanhelp))
- Fix TS definition for appSessionSecret [\#52](https://github.com/auth0/express-openid-connect/pull/52) ([joshcanhelp](https://github.com/joshcanhelp))
- Fix post logout redirect, add config for default [\#40](https://github.com/auth0/express-openid-connect/pull/40) ([balazsorban44](https://github.com/balazsorban44))
## [v0.6.0](https://github.com/auth0/express-openid-connect/tree/v0.6.0) (2020-01-14)

@@ -8,3 +26,3 @@ [Full Changelog](https://github.com/auth0/express-openid-connect/compare/v0.5.0...v0.6.0)

This release includes important changes to user session and token handling which will require an update for all applications.
This release includes important changes to user session and token handling which will require an update for all applications.

@@ -11,0 +29,0 @@ First, a new, required configuration key - `appSessionSecret`- has been added. The value here will be used to generate keys which are in turn used to encrypt the user identity returned from the identity provider. This encrypted and signed identity is stored in a cookie and used to populate the `req.openid.user` property, as before. This key should be set to either a secure, random value to use this built-in session or `false` to provide [your own custom application session handling](https://github.com/auth0/express-openid-connect/blob/master/EXAMPLES.md#4-custom-user-session-handling). A value for this can be generated with `openssl` like so:

@@ -140,6 +140,7 @@

response_type: 'code',
audience: process.env.API_URL,
audience: process.env.API_AUDIENCE,
scope: 'openid profile email read:reports'
},
handleCallback: async function (req, res, next) {
// Store recevied tokens (access and ID in this case) in server-side storage.
req.session.openidTokens = req.openidTokens;

@@ -173,3 +174,3 @@ next();

// API identifier to indicate which API this application will be calling.
audience: process.env.API_URL,
audience: process.env.API_AUDIENCE,
// Include the required scopes as well as offline_access to generate a refresh token.

@@ -204,6 +205,11 @@ scope: 'openid profile email read:reports offline_access'

// New tokenSet may not include a new refresh token.
tokenSet.refresh_token = tokenSet.refresh_token ?? refreshToken;
tokenSet.refresh_token = tokenSet.refresh_token || refreshToken;
// Where you store the refreshed tokenSet will depend on how the tokens are stored.
req.session.openidTokens = tokenSet;
// You can also refresh the session with a returned ID token.
// The req property below is the same as appSessionName, which defaults to "identity".
// If you're using custom session handling, the claims might be stored elsewhere.
req.identity.claims = tokenSet.claims();
}

@@ -237,1 +243,30 @@

```
## 8. Custom state handling
If your application needs to keep track of the request state before redirecting to log in, you can use the built-in state handling. By default, this library stores the post-callback redirect URL in a state object (along with a generated nonce) that is converted to a string, base64 encoded, and verified during callback (see [our documentation](https://auth0.com/docs/protocols/oauth2/oauth-state) for general information about this parameter). This state object can be added to and used during callback.
You can define a `getLoginState` configuration key set to a function that takes an Express `RequestHandler` and an options object and returns a plain object:
```js
app.use(auth({
getLoginState: function (req, options) {
// This object will be stringified and base64 URL-safe encoded.
return {
// Property used by the library for redirecting after logging in.
returnTo: '/custom-return-path',
// Additional properties as needed.
customProperty: req.someProperty,
};
},
handleCallback: function (req, res, next) {
// The req.openidState.customProperty is now available to use.
if ( req.openidState.customProperty ) {
// Do something ...
}
// Call next() to redirect to req.openidState.returnTo.
next();
}
}));
```

@@ -6,2 +6,23 @@ // Type definitions for express-openid-connect

interface OpenidRequest extends Request {
/**
* Library namespace for methods and data.
* See RequestContext and ResponseContext for how this is used.
*/
openid: object;
/**
* Decoded state for use in config.handleCallback().
*/
openidState: object;
/**
* Tokens for use in config.handleCallback().
*/
openidTokens: TokenSet;
}
/**
* Configuration parameters passed to the auth() middleware.
*/
interface ConfigParams {

@@ -25,5 +46,6 @@ /**

* REQUIRED. The secret(s) used to derive an encryption key for the user identity in a session cookie.
* Use a single string key or array of keys for an encrypted session cookie or false to skip.
* Can use env key APP_SESSION_SECRET instead.
*/
appSessionSecret: boolean | string | string[];
appSessionSecret?: boolean | string | string[];

@@ -70,5 +92,10 @@ /**

/**
* Function that returns a URL-safe state value for `res.openid.login()`.
*/
getLoginState?: (req: OpenidRequest, config: object) => object;
/**
* Function that returns the profile for `req.openid.user`.
*/
getUser?: (req: Request, config: ConfigParams) => undefined | UserinfoResponse;
getUser?: (req: OpenidRequest, config: ConfigParams) => undefined | UserinfoResponse;

@@ -78,3 +105,3 @@ /**

*/
handleCallback?: RequestHandler;
handleCallback?: (req: OpenidRequest, res: Response, next: NextFunction) => void;

@@ -123,2 +150,9 @@ /**

/**
* Either a relative path to the application or a valid URI to an external domain.
* This value must be registered on the authorization server.
* The user will be redirected to this after a logout has been performed.
*/
postLogoutRedirectUri?: string;
/**
* Relative path to the application callback to process the response from the authorization server.

@@ -139,8 +173,39 @@ */

/**
* Configuration parameters used in appSessionCookie.
*
* @see https://expressjs.com/en/api.html#res.cookie
*/
interface SessionCookieConfigParams {
/**
* Domain name for the cookie.
*/
domain?: string;
/**
* Set to true to use an ephemeral cookie.
* Defaults to false which will use appSessionDuration as the cookie expiration.
*/
ephemeral?: boolean;
/**
* Flags the cookie to be accessible only by the web server.
* Set to `true` by default in lib/config.
*/
httpOnly?: boolean;
/**
* Path for the cookie.
*/
path?: string;
/**
* Marks the cookie to be used with HTTPS only.
*/
secure?: boolean;
/**
* Value of the “SameSite” Set-Cookie attribute.
*/
sameSite?: string;
secure?: boolean;
}

@@ -147,0 +212,0 @@

@@ -52,5 +52,8 @@ const { strict: assert } = require('assert');

const value = encrypt(JSON.stringify(req[name]), { iat, uat, exp });
const expires = !duration ? 0 : new Date(exp * 1000);
res.cookie(name, value, {expires, ...cookieOptions});
const thisCookieOptions = Object.assign({}, cookieOptions);
thisCookieOptions.expires = cookieOptions.ephemeral ? 0 : new Date(exp * 1000);
delete thisCookieOptions.ephemeral;
res.cookie(name, value, thisCookieOptions);
}

@@ -57,0 +60,0 @@ }

@@ -15,2 +15,6 @@ const { Issuer, custom } = require('openid-client');

function spacedStringsToAlphabetical(string) {
return string.split(' ').sort().join(' ');
}
async function get(config) {

@@ -20,7 +24,7 @@

const issuerTokenAlgs = Array.isArray(issuer.id_token_signing_alg_values_supported) ?
const issuerTokenAlgs = Array.isArray(issuer.id_token_signing_alg_values_supported) ?
issuer.id_token_signing_alg_values_supported : [];
if (!issuerTokenAlgs.includes(config.idTokenAlg)) {
throw new Error(
`ID token algorithm "${config.idTokenAlg}" is not supported by the issuer. ` +
`ID token algorithm "${config.idTokenAlg}" is not supported by the issuer. ` +
`Supported ID token algorithms are: "${issuerTokenAlgs.join('", "')}". `

@@ -30,7 +34,8 @@ );

const configRespType = config.authorizationParams.response_type;
const configRespType = spacedStringsToAlphabetical(config.authorizationParams.response_type);
const issuerRespTypes = Array.isArray(issuer.response_types_supported) ? issuer.response_types_supported : [];
issuerRespTypes.map(spacedStringsToAlphabetical);
if (!issuerRespTypes.includes(configRespType)) {
throw new Error(
`Response type "${configRespType}" is not supported by the issuer. ` +
`Response type "${configRespType}" is not supported by the issuer. ` +
`Supported response types are: "${issuerRespTypes.join('", "')}". `

@@ -44,3 +49,3 @@ );

throw new Error(
`Response mode "${configRespMode}" is not supported by the issuer. ` +
`Response mode "${configRespMode}" is not supported by the issuer. ` +
`Supported response modes are "${issuerRespModes.join('", "')}". `

@@ -47,0 +52,0 @@ );

const Joi = require('@hapi/joi');
const clone = require('clone');
const loadEnvs = require('./loadEnvs');
const { defaultState: getLoginState } = require('./hooks/getLoginState');
const getUser = require('./hooks/getUser');
const handleCallback = require('./hooks/handleCallback');
const defaultAuthorizeParams = {
response_mode: 'form_post',
response_type: 'id_token',
scope: 'openid profile email'
};
const authorizationParamsSchema = Joi.object().keys({
response_mode: [Joi.string().optional(), Joi.allow(null).optional()],
response_type: Joi.string().required(),
scope: Joi.string().required()
}).unknown(true);
const appSessionCookieSchema = Joi.object().keys({
domain: Joi.string().optional(),
httpOnly: Joi.boolean().optional(),
path: Joi.string().optional(),
sameSite: Joi.string().valid(['Lax', 'Strict', 'None']).optional(),
secure: Joi.boolean().optional()
}).unknown(false);
const paramsSchema = Joi.object().keys({
appSessionCookie: Joi.object().optional(),
const paramsSchema = Joi.object({
appSessionCookie: Joi.object({
domain: Joi.string().optional(),
ephemeral: Joi.boolean().optional().default(false),
httpOnly: Joi.boolean().optional().default(true),
path: Joi.string().optional(),
sameSite: Joi.string().valid('Lax', 'Strict', 'None').optional().default('Lax'),
secure: Joi.boolean().optional()
}).optional().unknown(false).default(),
appSessionDuration: Joi.number().integer().optional().default(7 * 24 * 60 * 60),
appSessionName: Joi.string().token().optional().default('identity'),
appSessionSecret: Joi.alternatives([
// Single string key.
Joi.string(),
// Array of keys to allow for rotation.
Joi.array().items(Joi.string()),
// False to stop client session from being created.
Joi.boolean().valid([false])
Joi.boolean().valid(false)
]).required(),
auth0Logout: Joi.boolean().optional().default(false),
authorizationParams: Joi.object().optional(),
authorizationParams: Joi.object({
response_type: Joi.string().optional().default('id_token'),
scope: Joi.string().optional().default('openid profile email'),
response_mode: Joi.alternatives([
Joi.string().optional(),
Joi.allow(null).optional()
]).default(function(parent) {
const responseType = parent.response_type.split(' ');
const responseIncludesTokens = responseType.includes('id_token') || responseType.includes('token');
return responseIncludesTokens ? 'form_post' : undefined;
}),
}).optional().unknown(true).default(),
baseURL: Joi.string().uri().required(),
clientID: Joi.string().required(),
clientSecret: Joi.string().optional(),
clientSecret: Joi.string().when(
Joi.ref('authorizationParams.response_type', {adjust: (value) => value && value.split(' ').includes('code')}),
{
is: true,
then: Joi.string().required().messages({
'any.required': '"clientSecret" is required for response_type code'
}),
otherwise: Joi.when(
Joi.ref('idTokenAlg', {adjust: (value) => value && 'HS' === value.substring(0,2)}),
{
is: true,
then: Joi.string().required().messages({
'any.required': '"clientSecret" is required for ID tokens with HS algorithms'
})
}
)
}
),
clockTolerance: Joi.number().optional().default(60),
errorOnRequiredAuth: Joi.boolean().optional().default(false),
getUser: Joi.func().optional().default(getUser),
handleCallback: Joi.func().optional().default(handleCallback),
getLoginState: Joi.function().optional().default(() => getLoginState),
getUser: Joi.function().optional().default(() => getUser),
handleCallback: Joi.function().optional().default(() => handleCallback),
httpOptions: Joi.object().optional(),
identityClaimFilter: Joi.array().optional().default(['aud', 'iss', 'iat', 'exp', 'nonce', 'azp', 'auth_time']),
idpLogout: Joi.boolean().optional().default(false).when(
'auth0Logout', { is: true, then: Joi.boolean().optional().default(true) }
),
idpLogout: Joi.boolean().optional().default((parent) => parent.auth0Logout || false),
idTokenAlg: Joi.string().not('none').optional().default('RS256'),
issuerBaseURL: Joi.alternatives([ Joi.string().uri(), Joi.string().hostname() ]).required(),
issuerBaseURL: Joi.alternatives([
Joi.string().uri(),
Joi.string().hostname()
]).required(),
legacySameSiteCookie: Joi.boolean().optional().default(true),
loginPath: Joi.string().uri({relativeOnly: true}).optional().default('/login'),
logoutPath: Joi.string().uri({relativeOnly: true}).optional().default('/logout'),
postLogoutRedirectUri: Joi.string().uri({allowRelative: true}).optional().default(''),
redirectUriPath: Joi.string().uri({relativeOnly: true}).optional().default('/callback'),
required: Joi.alternatives([ Joi.func(), Joi.boolean()]).optional().default(true),
required: Joi.alternatives([ Joi.function(), Joi.boolean()]).optional().default(true),
routes: Joi.boolean().optional().default(true),
});
function buildAuthorizeParams(authorizationParams) {
/*
If the user does not provide authorizationParams we default to "defaultAuthorizeParams" (id_token/form_post).
If the user provides authorizationParams then
- the default response_mode is DEFAULT (undefined),
- the default scope is defaultAuthorizeParams.scope
- response type is required
*/
authorizationParams = authorizationParams && Object.keys(authorizationParams).length > 0 ?
authorizationParams :
clone(defaultAuthorizeParams);
if (!authorizationParams.scope) {
authorizationParams.scope = defaultAuthorizeParams.scope;
}
const authParamsValidation = Joi.validate(authorizationParams, authorizationParamsSchema);
if(authParamsValidation.error) {
throw new Error(authParamsValidation.error.details[0].message);
}
return authorizationParams;
}
function buildAppSessionCookieConfig(cookieConfig) {
cookieConfig = cookieConfig && Object.keys(cookieConfig).length ? cookieConfig : {};
cookieConfig = Object.assign({ httpOnly: true }, cookieConfig);
const cookieConfigValidation = Joi.validate(cookieConfig, appSessionCookieSchema);
if(cookieConfigValidation.error) {
throw new Error(cookieConfigValidation.error.details[0].message);
}
return cookieConfig;
}
module.exports.get = function(params) {
let config = typeof params == 'object' ? clone(params) : {};
let config = (typeof params == 'object' ? clone(params) : {});
config = Object.assign({
issuerBaseURL: process.env.ISSUER_BASE_URL,
baseURL: process.env.BASE_URL,
clientID: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
appSessionSecret: process.env.APP_SESSION_SECRET,
}, config);
loadEnvs(config);
const paramsValidation = Joi.validate(config, paramsSchema);
if(paramsValidation.error) {
const paramsValidation = paramsSchema.validate(config);
if (paramsValidation.error) {
throw new Error(paramsValidation.error.details[0].message);
}
config = paramsValidation.value;
config.authorizationParams = buildAuthorizeParams(config.authorizationParams);
config.appSessionCookie = buildAppSessionCookieConfig(config.appSessionCookie);
// Code grant requires a client secret to exchange the code for tokens
const responseTypeHasCode = config.authorizationParams.response_type.split(' ').includes('code');
if (responseTypeHasCode && !config.clientSecret) {
throw new Error('"clientSecret" is required for response_type code');
}
// HS256 ID tokens require a client secret to validate the signature.
if ('HS' === config.idTokenAlg.substring(0,2) && !config.clientSecret) {
throw new Error('"clientSecret" is required for ID tokens with HS algorithms');
}
return config;
return paramsValidation.value;
};
const cb = require('cb');
const url = require('url');
const urlJoin = require('url-join');
const { TokenSet } = require('openid-client');
const transient = require('./transientHandler');
const { get: getClient } = require('./client');
const { TokenSet } = require('openid-client');
const { encodeState } = require('../lib/hooks/getLoginState');

@@ -48,3 +51,3 @@ class RequestContext {

async login(params = {}) {
async login(options = {}) {
const next = cb(this._next).once();

@@ -54,28 +57,40 @@ const req = this._req;

const config = this._config;
const client = req.openid.client;
const client = req.openid.client;
const authorizeParams = config.authorizationParams;
// Set default returnTo value, allow passed-in options to override.
options = {
returnTo: this._config.baseURL,
authorizationParams: {},
...options
};
// Ensure a redirect_uri, merge in configuration options, then passed-in options.
options.authorizationParams = {
redirect_uri: this.getRedirectUri(),
...config.authorizationParams,
...options.authorizationParams
};
const transientOpts = {
legacySameSiteCookie: config.legacySameSiteCookie,
sameSite: config.authorizationParams.response_mode === 'form_post' ? 'None' : 'Lax'
sameSite: options.authorizationParams.response_mode === 'form_post' ? 'None' : 'Lax'
};
try {
let returnTo;
if (params.returnTo) {
returnTo = params.returnTo;
} else if (req.method === 'GET') {
returnTo = req.originalUrl;
} else {
returnTo = this._config.baseURL;
}
let stateValue = await config.getLoginState(req, options);
if ( typeof stateValue !== 'object' ) {
next(new Error( 'Custom state value must be an object.' ));
}
stateValue.nonce = transient.createNonce();
// TODO: Store this in state
transient.store('returnTo', res, Object.assign({value: returnTo}, transientOpts));
const stateTransientOpts = {
...transientOpts,
value: encodeState(stateValue)
};
const authParams = Object.assign({
try {
const authParams = {
...options.authorizationParams,
nonce: transient.store('nonce', res, transientOpts),
state: transient.store('state', res, transientOpts),
redirect_uri: this.getRedirectUri()
}, authorizeParams, params.authorizationParams || {});
state: transient.store('state', res, stateTransientOpts)
};

@@ -93,4 +108,13 @@ const authorizationUrl = client.authorizationUrl(authParams);

const res = this._res;
const returnURL = params.returnTo || this._config.baseURL;
let returnURL = params.returnTo || req.query.returnTo || this._config.postLogoutRedirectUri;
if (url.parse(returnURL).host === null) {
returnURL = urlJoin(this._config.baseURL, returnURL);
}
if (!req.isAuthenticated()) {
return res.redirect(returnURL);
}
req[this._config.appSessionName] = undefined;

@@ -102,12 +126,13 @@

const client = this._req.openid.client;
try {
const client = this._req.openid.client;
const url = client.endSessionUrl({
returnURL = client.endSessionUrl({
post_logout_redirect_uri: returnURL,
id_token_hint: req.openid.tokens,
});
res.redirect(url);
} catch(err) {
next(err);
return next(err);
}
res.redirect(returnURL);
}

@@ -114,0 +139,0 @@

const crypto = require('crypto');
exports.store = store;
exports.getOnce = getOnce;
exports.createNonce = createNonce;
/**

@@ -87,5 +91,1 @@ * Set a cookie with a value or a generated nonce.

}
exports.store = store;
exports.getOnce = getOnce;
exports.createNonce = createNonce;

@@ -12,2 +12,3 @@ const express = require('express');

const appSession = require('../lib/appSession');
const { decodeState } = require('../lib/hooks/getLoginState');

@@ -28,7 +29,2 @@ const enforceLeadingSlash = (path) => {

const authorizeParams = config.authorizationParams;
if (typeof express.Router === 'undefined') {
throw new Error(`express-openid-connect needs express@^3, current installed version ${require('express/package').version}`);
}
const router = express.Router();

@@ -46,4 +42,6 @@

// Express context and OpenID Issuer discovery.
router.use(async (req, res, next) => {
req.openid = new RequestContext(config, req, res, next);
try {

@@ -54,2 +52,3 @@ await req.openid.load();

}
res.openid = new ResponseContext(config, req, res, next);

@@ -61,63 +60,70 @@ req.isAuthenticated = () => req.openid.isAuthenticated;

if (config.routes) {
router.get(enforceLeadingSlash(config.loginPath), express.urlencoded({ extended: false }), (req, res) => {
res.openid.login({ returnTo: config.baseURL });
});
router.get(enforceLeadingSlash(config.logoutPath), (req, res) => res.openid.logout());
}
let callbackMethod;
// Login route, configurable with loginPath.
router.get(
enforceLeadingSlash(config.loginPath),
express.urlencoded({ extended: false }),
(req, res) => {
res.openid.login({ returnTo: config.baseURL });
}
);
switch (authorizeParams.response_mode) {
case 'form_post':
callbackMethod = 'post';
break;
case 'query':
callbackMethod = 'get';
break;
default:
callbackMethod = 'get';
// Logout route, configured with logoutPath.
router.get(
enforceLeadingSlash(config.logoutPath),
(req, res) => res.openid.logout()
);
}
router[callbackMethod](enforceLeadingSlash(config.redirectUriPath), express.urlencoded({ extended: false }), cookieParser(), async (req, res, next) => {
next = cb(next).once();
try {
const redirect_uri = res.openid.getRedirectUri();
const client = req.openid.client;
const transientOpts = { legacySameSiteCookie: config.legacySameSiteCookie };
const callbackMethod = ('form_post' === authorizeParams.response_mode ? 'post' : 'get');
const transientOpts = { legacySameSiteCookie: config.legacySameSiteCookie };
let tokenSet;
// Callback route, configured with redirectUriPath.
router[callbackMethod](
enforceLeadingSlash(config.redirectUriPath),
express.urlencoded({ extended: false }),
cookieParser(),
async (req, res, next) => {
next = cb(next).once();
try {
const callbackParams = client.callbackParams(req);
tokenSet = await client.callback(redirect_uri, callbackParams, {
nonce: transient.getOnce('nonce', req, res, transientOpts),
state: transient.getOnce('state', req, res, transientOpts),
response_type: authorizeParams.response_type,
});
} catch (err) {
throw createError.BadRequest(err.message);
}
const redirectUri = res.openid.getRedirectUri();
const client = req.openid.client;
req.openidTokens = tokenSet;
const returnedState = transient.getOnce('state', req, res, transientOpts);
if (config.appSessionSecret) {
let identityClaims = tokenSet.claims();
config.identityClaimFilter.forEach(claim => {
delete identityClaims[claim];
});
let tokenSet;
try {
const callbackParams = client.callbackParams(req);
tokenSet = await client.callback(redirectUri, callbackParams, {
nonce: transient.getOnce('nonce', req, res, transientOpts),
state: returnedState,
response_type: authorizeParams.response_type,
});
} catch (err) {
throw createError.BadRequest(err.message);
}
req[config.appSessionName].claims = identityClaims;
req.openidState = decodeState(returnedState);
req.openidTokens = tokenSet;
if (config.appSessionSecret) {
let identityClaims = tokenSet.claims();
config.identityClaimFilter.forEach(claim => {
delete identityClaims[claim];
});
req[config.appSessionName].claims = identityClaims;
}
next();
} catch (err) {
next(err);
}
next();
} catch (err) {
next(err);
},
config.handleCallback,
function (req, res) {
res.redirect(req.openidState.returnTo || config.baseURL);
}
},
config.handleCallback,
function (req, res) {
const transientOpts = { legacySameSiteCookie: config.legacySameSiteCookie };
const returnTo = transient.getOnce('returnTo', req, res, transientOpts) || config.baseURL;
res.redirect(returnTo);
});
);

@@ -136,4 +142,3 @@ if (config.required) {

//We do this to either speed up the first request
// or fail fast, the first request
// Fail on initialization if config is invalid.
getClient(config);

@@ -140,0 +145,0 @@

{
"name": "express-openid-connect",
"version": "0.6.0",
"version": "0.7.0",
"description": "Express middleware to protect web applications using OpenID Connect.",

@@ -11,6 +11,10 @@ "homepage": "https://github.com/auth0/express-openid-connect",

"lint": "eslint . --ignore-path .gitignore",
"test": "mocha"
"test": "mocha",
"test:ci": "nyc --reporter=lcov npm test"
},
"peerDependencies": {
"express": ">= 4.17.0"
},
"dependencies": {
"@hapi/joi": "^14.5.0",
"@hapi/joi": "^16.1.8",
"cb": "^0.1.0",

@@ -20,7 +24,7 @@ "clone": "^2.1.2",

"cookie-parser": "^1.4.4",
"futoin-hkdf": "^1.2.1",
"futoin-hkdf": "^1.3.1",
"http-errors": "^1.7.3",
"jose": "^1.17.2",
"jose": "^1.22.2",
"on-headers": "^1.0.2",
"openid-client": "^3.9.2",
"openid-client": "^3.12.2",
"p-memoize": "^3.1.0",

@@ -30,5 +34,5 @@ "url-join": "^4.0.1"

"devDependencies": {
"@types/express": "^4.17.1",
"@types/express": "^4.17.2",
"chai": "^4.2.0",
"cookie-session": "^2.0.0-beta.3",
"cookie-session": "^2.0.0-rc.1",
"eslint": "^5.16.0",

@@ -38,7 +42,8 @@ "express": "^4.17.1",

"jsonwebtoken": "^8.5.1",
"mocha": "^6.2.1",
"nock": "^11.4.0",
"mocha": "^6.2.2",
"nock": "^11.8.2",
"nyc": "^15.0.0",
"pem-jwk": "^2.0.0",
"proxyquire": "^2.1.3",
"request-promise-native": "^1.0.7",
"request-promise-native": "^1.0.8",
"selfsigned": "^1.10.7",

@@ -45,0 +50,0 @@ "sinon": "^7.5.0"

@@ -8,7 +8,9 @@ # Express OpenID Connect

- Node v10.13 or higher
- Express v4.16 or higher
- Express v4.17 or higher
**Please Note:** This library is currently in pre-release status and has not had a complete security review. We **do not** recommend using this library in production yet. As we move towards early access, please be aware that releases may contain breaking changes. We will be monitoring the Issues queue here for feedback and questions. PRs and comments on existing PRs are welcome!
[![Build Status](https://travis-ci.org/auth0/express-openid-connect.svg?branch=master)](https://travis-ci.org/auth0/express-openid-connect)
[![CircleCI](https://img.shields.io/circleci/build/github/auth0/express-openid-connect/master?style=flat-square)](https://circleci.com/gh/auth0/express-openid-connect/tree/master)
[![codecov](https://img.shields.io/codecov/c/github/auth0/express-openid-connect?style=flat-square)](https://codecov.io/gh/auth0/express-openid-connect)
[![NPM version](https://img.shields.io/npm/v/express-openid-connect.svg?style=flat-square)](https://npmjs.org/package/express-openid-connect)

@@ -44,2 +46,4 @@

Follow our [Secure Local Development guide](https://auth0.com/docs/libraries/secure-local-development) to ensure that applications using this library are running over secure channels (HTTPS URLs). Applications using this library without HTTPS may experience "invalid state" errors.
The library needs [the following required configuration keys](https://github.com/auth0/express-openid-connect/blob/master/API.md#required-keys) to request and accept authentication. These can be configured in a `.env` file in the root of your application:

@@ -61,9 +65,11 @@

const { auth } = require('express-openid-connect');
app.use(auth({
issuerBaseURL: 'https://YOUR_DOMAIN',
baseURL: 'https://YOUR_APPLICATION_ROOT_URL',
clientID: 'YOUR_CLIENT_ID',
appSessionKey: 'LONG_RANDOM_STRING'
}));
const { auth } = require("express-openid-connect");
app.use(
auth({
issuerBaseURL: "https://YOUR_DOMAIN",
baseURL: "https://YOUR_APPLICATION_ROOT_URL",
clientID: "YOUR_CLIENT_ID",
appSessionSecret: "LONG_RANDOM_STRING"
})
);
```

@@ -70,0 +76,0 @@

@@ -8,3 +8,3 @@ const assert = require('chai').assert;

secret: '__test_secret__',
duration: 1234567890,
duration: 3155760000, // 100 years
cookieOptions: {}

@@ -16,3 +16,2 @@ };

};
const res = {};
const next = () => true;

@@ -24,3 +23,3 @@

const appSessionMw = appSession(defaultConfig);
const result = appSessionMw(req, res, next);
const result = appSessionMw(req, {}, next);

@@ -39,3 +38,3 @@ it('should call next', function() {

const thisReq = Object.assign({}, req, {identity: {sub: '__test_existing_sub__'}});
const result = appSessionMw(thisReq, res, next);
const result = appSessionMw(thisReq, {}, next);

@@ -56,3 +55,3 @@ it('should call next', function() {

it('should error with malformed identity', function() {
assert.throws(() => appSessionMw(thisReq, res, next), Error, 'JWE malformed or invalid serialization');
assert.throws(() => appSessionMw(thisReq, {}, next), Error, 'JWE malformed or invalid serialization');
});

@@ -66,7 +65,81 @@ });

it('should set the identity on req', function() {
const result = appSessionMw(thisReq, res, next);
const result = appSessionMw(thisReq, {}, next);
assert.ok(result);
assert.equal(thisReq.identity.sub, '__test_sub__');
});
});
describe('sessioncookie options', () => {
let cookieArgs;
const thisRes = {
cookie: function cookie() {cookieArgs = JSON.parse(JSON.stringify(arguments)); },
writeHead: () => null,
setHeader: () => null
};
beforeEach(function() {
cookieArgs = {};
});
it('should set the correct cookie by default', function() {
const thisReq = {get: () => 'identity=' + sessionEncryption.encrypted};
const appSessionMw = appSession(defaultConfig);
const result = appSessionMw(thisReq, thisRes, next);
thisRes.writeHead();
assert.ok(result);
assert.equal(cookieArgs['0'], 'identity');
assert.isNotEmpty(cookieArgs['1']);
assert.isObject(cookieArgs['2']);
assert.hasAllKeys(cookieArgs['2'], ['expires']);
const expDate = new Date(cookieArgs['2'].expires);
assert.equal( (expDate.getFullYear() - (new Date()).getFullYear()), 100);
});
it('should set the correct custom cookie name', function() {
const thisReq = {get: () => 'customName=' + sessionEncryption.encrypted};
const customConfig = Object.assign({}, defaultConfig, {name: 'customName'});
const appSessionMw = appSession(customConfig);
const result = appSessionMw(thisReq, thisRes, next);
thisRes.writeHead();
assert.ok(result);
assert.equal(cookieArgs['0'], 'customName');
});
it('should set an ephemeral cookie', function() {
const thisReq = {get: () => 'identity=' + sessionEncryption.encrypted};
const customConfig = Object.assign({}, defaultConfig, {cookieOptions: {ephemeral: true}});
const appSessionMw = appSession(customConfig);
const result = appSessionMw(thisReq, thisRes, next);
thisRes.writeHead();
assert.ok(result);
assert.equal(cookieArgs['2'].expires, 0);
});
it('should pass custom cookie options', function() {
const thisReq = {get: () => 'identity=' + sessionEncryption.encrypted};
const cookieOptConfig = {cookieOptions: {
domain: '__test_domain__',
path: '__test_path__',
secure: true,
httpOnly: false,
sameSite: '__test_samesite__',
}};
const customConfig = Object.assign({}, defaultConfig, cookieOptConfig);
const appSessionMw = appSession(customConfig);
const result = appSessionMw(thisReq, thisRes, next);
thisRes.writeHead();
assert.ok(result);
assert.equal(cookieArgs['2'].domain, '__test_domain__');
assert.equal(cookieArgs['2'].path, '__test_path__');
assert.equal(cookieArgs['2'].secure, true);
assert.equal(cookieArgs['2'].httpOnly, false);
assert.equal(cookieArgs['2'].sameSite, '__test_samesite__');
});
});
});

@@ -8,4 +8,7 @@ const assert = require('chai').assert;

const { decodeState } = require('../lib/hooks/getLoginState');
const expressOpenid = require('..');
const server = require('./fixture/server');
const filterRoute = (method, path) => {

@@ -33,15 +36,20 @@ return r => r.route &&

const defaultConfig = {
appSessionSecret: '__test_session_secret__',
clientID: '__test_client_id__',
baseURL: 'https://example.org',
issuerBaseURL: 'https://test.auth0.com',
required: false
};
function getRouter (customConfig) {
return expressOpenid.auth(Object.assign({}, defaultConfig, customConfig));
}
describe('auth', function() {
describe('default', () => {
let baseUrl, router;
before(async function() {
router = expressOpenid.auth({
appSessionSecret: '__test_session_secret__',
clientID: '__test_client_id__',
baseURL: 'https://example.org',
issuerBaseURL: 'https://test.auth0.com',
required: false
});
router = getRouter();
baseUrl = await server.create(router);

@@ -91,13 +99,6 @@ });

before(async function() {
router = expressOpenid.auth({
appSessionSecret: '__test_session_secret__',
clientID: '__test_client_id__',
baseURL: 'https://example.org',
issuerBaseURL: 'https://test.auth0.com',
authorizationParams: {
response_mode: undefined,
response_type: 'none',
},
required: false
});
router = getRouter({authorizationParams: {
response_mode: undefined,
response_type: 'none',
}});
baseUrl = await server.create(router);

@@ -130,12 +131,7 @@ });

describe('response_type=code', () => {
let baseUrl;
let router;
let baseUrl, router;
before(async function() {
router = expressOpenid.auth({
appSessionSecret: '__test_session_secret__',
clientID: '__test_client_id__',
router = getRouter({
clientSecret: '__test_client_secret__',
baseURL: 'https://example.org',
issuerBaseURL: 'https://test.auth0.com',
authorizationParams: {

@@ -149,3 +145,2 @@ response_mode: undefined,

it('should redirect to the authorize url properly on /login', async function() {

@@ -179,16 +174,9 @@ const cookieJar = request.jar();

describe('response_type=id_token', () => {
let router;
let baseUrl;
let baseUrl, router;
before(async function() {
router = expressOpenid.auth({
appSessionSecret: '__test_session_secret__',
clientID: '__test_client_id__',
baseURL: 'https://example.org',
issuerBaseURL: 'https://test.auth0.com',
authorizationParams: {
response_mode: undefined,
response_type: 'id_token',
}
});
router = getRouter({authorizationParams: {
response_mode: undefined,
response_type: 'id_token',
}});
baseUrl = await server.create(router);

@@ -209,3 +197,3 @@ });

assert.equal(parsed.query.response_type, 'id_token');
assert.equal(parsed.query.response_mode, undefined);
assert.equal(parsed.query.response_mode, 'form_post');
assert.equal(parsed.query.redirect_uri, 'https://example.org/callback');

@@ -217,3 +205,3 @@ assert.property(parsed.query, 'nonce');

it('should contain the two callbacks route', function() {
assert.ok(router.stack.some(filterRoute('GET', '/callback')));
assert.ok(router.stack.some(filterRoute('POST', '/callback')));
});

@@ -225,11 +213,6 @@

describe('custom path values', () => {
let baseUrl, router;
before(async function() {
router = expressOpenid.auth({
appSessionSecret: '__test_session_secret__',
clientID: '__test_client_id__',
baseURL: 'https://example.org',
issuerBaseURL: 'https://test.auth0.com',
router = getRouter({
redirectUriPath: 'custom-callback',

@@ -239,2 +222,3 @@ loginPath: 'custom-login',

});
baseUrl = await server.create(router);

@@ -267,2 +251,65 @@ });

});
describe('custom login parameter values', () => {
it('should redirect to the authorize url properly on /login', async function() {
const router = getRouter({routes: false});
router.get('/login', (req, res) => {
res.openid.login({
returnTo: 'https://example.org/custom-redirect',
authorizationParams: {
response_type: 'code',
response_mode: 'query',
scope: 'openid email',
}
});
});
const baseUrl = await server.create(router);
const cookieJar = request.jar();
const res = await request.get('/login', { cookieJar, baseUrl, followRedirect: false });
assert.equal(res.statusCode, 302);
const parsed = url.parse(res.headers.location, true);
assert.equal(parsed.hostname, 'test.auth0.com');
assert.equal(parsed.pathname, '/authorize');
assert.equal(parsed.query.scope, 'openid email');
assert.equal(parsed.query.response_type, 'code');
assert.equal(parsed.query.response_mode, 'query');
assert.equal(parsed.query.redirect_uri, 'https://example.org/callback');
assert.property(parsed.query, 'nonce');
const decodedState = decodeState(parsed.query.state);
assert.equal(decodedState.returnTo, 'https://example.org/custom-redirect');
assert.isTrue(decodedState.nonce.length >= 16);
assert.notEqual(decodedState.nonce, parsed.query.nonce);
});
});
describe('custom state building', () => {
it('should use a custom state builder', async function() {
const router = getRouter({getLoginState: (req, opts) => {
return {
returnTo: opts.returnTo + '/custom-page',
customProp: '__test_custom_prop__',
};
}});
const baseUrl = await server.create(router);
const cookieJar = request.jar();
const res = await request.get('/login', { cookieJar, baseUrl, followRedirect: false });
assert.equal(res.statusCode, 302);
const parsed = url.parse(res.headers.location, true);
const decodedState = decodeState(parsed.query.state);
assert.equal(decodedState.returnTo, 'https://example.org/custom-page');
assert.equal(decodedState.customProp, '__test_custom_prop__');
});
});
});

@@ -8,2 +8,3 @@ const assert = require('chai').assert;

const { encodeState } = require('../lib/hooks/getLoginState');
const expressOpenid = require('..');

@@ -13,2 +14,3 @@ const server = require('./fixture/server');

const clientID = '__test_client_id__';
const expectedDefaultState = encodeState({ returnTo: 'https://example.org' });

@@ -139,3 +141,6 @@ function testCase(params) {

it('should return the reason to the error handler', function() {
assert.match(this.response.body.err.message, /unexpected token/i);
assert.equal(
this.response.body.err.message,
'failed to decode JWT (JWTMalformed: JWTs must have three components)'
);
});

@@ -187,4 +192,3 @@ }

cookies: {
state: '__test_state__',
returnTo: '/return-to'
state: '__test_state__'
},

@@ -204,8 +208,7 @@ body: {

cookies: {
_state: '__test_state__',
_nonce: '__test_nonce__',
_returnTo: '/return-to'
_state: expectedDefaultState,
_nonce: '__test_nonce__'
},
body: {
state: '__test_state__',
state: expectedDefaultState,
id_token: makeIdToken()

@@ -219,3 +222,3 @@ },

it('should redirect to the intended url', function() {
assert.equal(this.response.headers['location'], '/return-to');
assert.equal(this.response.headers['location'], 'https://example.org');
});

@@ -279,7 +282,7 @@

cookies: {
_state: '__test_state__',
_state: expectedDefaultState,
_nonce: '__test_nonce__'
},
body: {
state: '__test_state__',
state: expectedDefaultState,
id_token: makeIdToken()

@@ -299,7 +302,7 @@ },

cookies: {
_state: '__test_state__',
_state: expectedDefaultState,
_nonce: '__test_nonce__'
},
body: {
state: '__test_state__',
state: expectedDefaultState,
id_token: makeIdToken()

@@ -306,0 +309,0 @@ },

const { assert } = require('chai');
const { get: getConfig } = require('../lib/config');
const defaultConfig = {
appSessionSecret: '__test_session_secret__',
clientID: '__test_client_id__',
issuerBaseURL: 'https://test.auth0.com',
baseURL: 'https://example.org'
};
describe('config', function() {
describe('simple case', function() {
const config = getConfig({
appSessionSecret: false,
clientID: '__test_client_id__',
issuerBaseURL: 'https://test.auth0.com',
baseURL: 'https://example.org',
});
const config = getConfig(defaultConfig);

@@ -21,7 +23,7 @@ it('should default to response_type=id_token', function() {

it('should default to scope=openid profile email ', function() {
it('should default to scope=openid profile email', function() {
assert.equal(config.authorizationParams.scope, 'openid profile email');
});
it('should default to required true ', function() {
it('should default to required true', function() {
assert.ok(config.required);

@@ -31,9 +33,5 @@ });

describe('when authorizationParams is response_type=x', function() {
const config = getConfig({
appSessionSecret: '__test_session_secret__',
clientID: '__test_client_id__',
describe('when authorizationParams is response_type=code', function() {
const customConfig = Object.assign({}, defaultConfig, {
clientSecret: '__test_client_secret__',
issuerBaseURL: 'https://test.auth0.com',
baseURL: 'https://example.org',
authorizationParams: {

@@ -43,12 +41,13 @@ response_type: 'code'

});
const config = getConfig(customConfig);
it('should default to response_type=id_token', function() {
it('should set new response_type', function() {
assert.equal(config.authorizationParams.response_type, 'code');
});
it('should default to response_mode=form_post', function() {
it('should allow undefined response_mode', function() {
assert.equal(config.authorizationParams.response_mode, undefined);
});
it('should default to scope=openid profile email ', function() {
it('should keep default scope', function() {
assert.equal(config.authorizationParams.scope, 'openid profile email');

@@ -58,11 +57,19 @@ });

describe('with auth0Logout', function() {
const config = getConfig({
appSessionSecret: '__test_session_secret__',
clientID: '__test_client_id__',
issuerBaseURL: 'https://test.auth0.com',
baseURL: 'https://example.org',
auth0Logout: true
describe('when authorizationParams response_type fuzzy matches issuer', function() {
const customConfig = Object.assign({}, defaultConfig, {
clientSecret: '__test_client_secret__',
authorizationParams: {
response_type: 'token id_token code'
}
});
const config = getConfig(customConfig);
it('should keep token code', function() {
assert.equal(config.authorizationParams.response_type, 'token id_token code');
});
});
describe('with auth0Logout', function() {
const config = getConfig(Object.assign({}, defaultConfig, {auth0Logout: true}));
it('should set idpLogout to true', function() {

@@ -75,8 +82,3 @@ assert.equal(config.auth0Logout, true);

describe('without auth0Logout nor idpLogout', function() {
const config = getConfig({
appSessionSecret: '__test_session_secret__',
clientID: '__test_client_id__',
issuerBaseURL: 'https://test.auth0.com',
baseURL: 'https://example.org',
});
const config = getConfig(defaultConfig);

@@ -90,9 +92,3 @@ it('should set both to false', function() {

describe('with idpLogout', function() {
const config = getConfig({
appSessionSecret: '__test_session_secret__',
clientID: '__test_client_id__',
issuerBaseURL: 'https://test.auth0.com',
baseURL: 'https://example.org',
idpLogout: true
});
const config = getConfig(Object.assign({}, defaultConfig, {idpLogout: true}));

@@ -106,8 +102,3 @@ it('should set both to false', function() {

describe('default auth paths', function() {
const config = getConfig({
appSessionSecret: '__test_session_secret__',
clientID: '__test_client_id__',
issuerBaseURL: 'https://test.auth0.com',
baseURL: 'https://example.org',
});
const config = getConfig(defaultConfig);

@@ -128,7 +119,3 @@ it('should set the default callback path', function() {

describe('custom auth paths', function() {
const config = getConfig({
appSessionSecret: '__test_session_secret__',
clientID: '__test_client_id__',
issuerBaseURL: 'https://test.auth0.com',
baseURL: 'https://example.org',
const customConfig = Object.assign({}, defaultConfig, {
redirectUriPath: '/custom-callback',

@@ -138,2 +125,3 @@ loginPath: '/custom-login',

});
const config = getConfig(customConfig);

@@ -154,8 +142,3 @@ it('should accept the custom callback path', function() {

describe('app session default configuration', function() {
const config = getConfig({
appSessionSecret: '__test_session_secret__',
clientID: '__test_client_id__',
issuerBaseURL: 'https://test.auth0.com',
baseURL: 'https://example.org'
});
const config = getConfig(defaultConfig);

@@ -178,3 +161,3 @@ it('should set the app session secret', function() {

assert.notExists(config.appSessionCookie.secure);
assert.notExists(config.appSessionCookie.sameSite);
assert.equal(config.appSessionCookie.sameSite, 'Lax');
assert.equal(config.appSessionCookie.httpOnly, true);

@@ -185,3 +168,3 @@ });

describe('app session cookie configuration', function() {
const config = getConfig({
const customConfig = Object.assign({}, defaultConfig, {
appSessionSecret: [ '__test_session_secret_1__', '__test_session_secret_2__' ],

@@ -193,12 +176,11 @@ appSessionName: '__test_custom_session_name__',

path: '__test_custom_path__',
ephemeral: true,
httpOnly: false,
secure: true,
sameSite: 'Lax',
},
clientID: '__test_client_id__',
issuerBaseURL: 'https://test.auth0.com',
baseURL: 'https://example.org'
sameSite: 'Strict',
}
});
it('should set an array of secrets', function() {
const config = getConfig(customConfig);
assert.equal(config.appSessionSecret.length, 2);

@@ -210,2 +192,3 @@ assert.equal(config.appSessionSecret[0], '__test_session_secret_1__');

it('should set the custom session values', function() {
const config = getConfig(customConfig);
assert.equal(config.appSessionDuration, 1234567890);

@@ -216,10 +199,11 @@ assert.equal(config.appSessionName, '__test_custom_session_name__');

it('should set the session cookie attributes to custom values', function() {
const config = getConfig(customConfig);
assert.equal(config.appSessionCookie.domain, '__test_custom_domain__');
assert.equal(config.appSessionCookie.path, '__test_custom_path__');
assert.equal(config.appSessionCookie.ephemeral, true);
assert.equal(config.appSessionCookie.httpOnly, false);
assert.equal(config.appSessionCookie.secure, true);
assert.equal(config.appSessionCookie.sameSite, 'Lax');
assert.equal(config.appSessionCookie.sameSite, 'Strict');
});
});
});

@@ -24,3 +24,3 @@ const { assert } = require('chai');

});
}, '"issuerBaseURL" must be a valid uri');
}, '"issuerBaseURL" does not match any of the allowed types');
});

@@ -98,3 +98,3 @@

expressOpenid.auth(getTestConfig({appSessionSecret: {key: '__test_session_secret__'}}));
}, '"appSessionSecret" must be a string');
}, '"appSessionSecret" must be one of [string, array, false]');
});

@@ -109,3 +109,3 @@

}));
}, '"httpOnly" must be a boolean');
}, '"appSessionCookie.httpOnly" must be a boolean');
});

@@ -120,3 +120,3 @@

}));
}, '"secure" must be a boolean');
}, '"appSessionCookie.secure" must be a boolean');
});

@@ -131,3 +131,3 @@

}));
}, '"sameSite" must be one of [Lax, Strict, None]');
}, '"appSessionCookie.sameSite" must be one of [Lax, Strict, None]');
});

@@ -142,3 +142,3 @@

}));
}, '"domain" must be a string');
}, '"appSessionCookie.domain" must be a string');
});

@@ -153,4 +153,4 @@

}));
}, '"path" must be a string');
}, '"appSessionCookie.path" must be a string');
});
});
const { assert } = require('chai');
const url = require('url');
const server = require('./fixture/server');

@@ -86,9 +85,3 @@ const { auth } = require('./..');

assert.equal(logoutResponse.statusCode, 302);
const parsedUrl = url.parse(logoutResponse.headers.location, true);
assert.deepInclude(parsedUrl, {
protocol: 'https:',
hostname: 'test.auth0.com',
query: { returnTo: 'https://example.org', client_id: '__test_client_id__' },
pathname: '/v2/logout',
});
assert.equal(logoutResponse.headers.location, 'https://example.org');
});

@@ -98,2 +91,77 @@ });

});
describe('should use postLogoutRedirectUri if present', function() {
describe('should allow relative paths, and prepend with baseURL', () => {
let baseUrl;
const jar = request.jar();
before(async function() {
const middleware = auth({
idpLogout: false,
clientID: '__test_client_id__',
baseURL: 'https://example.org',
issuerBaseURL: 'https://test.auth0.com',
appSessionSecret: '__test_session_secret__',
postLogoutRedirectUri: '/after-logout-in-auth-config',
required: false,
});
baseUrl = await server.create(middleware);
await request.post({
uri: '/session',
json: {
openidTokens: {
id_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
}
},
baseUrl, jar
});
});
it('should redirect to postLogoutRedirectUri in auth() config', async function() {
const logoutResponse = await request.get({uri: '/logout', baseUrl, jar, followRedirect: false});
assert.equal(logoutResponse.headers.location, 'https://example.org/after-logout-in-auth-config');
});
it('should redirect to returnTo in logout query', async function() {
const logoutResponse = await request.get({uri: '/logout', qs: {returnTo: '/after-logout-in-logout-query'}, baseUrl, jar, followRedirect: false});
assert.equal(logoutResponse.headers.location, 'https://example.org/after-logout-in-logout-query');
});
});
describe('should allow absolute paths', () => {
let baseUrl;
const jar = request.jar();
before(async function() {
const middleware = auth({
idpLogout: false,
clientID: '__test_client_id__',
baseURL: 'https://example.org',
issuerBaseURL: 'https://test.auth0.com',
appSessionSecret: '__test_session_secret__',
postLogoutRedirectUri: 'https://external-domain.com/after-logout-in-auth-config',
required: false,
});
baseUrl = await server.create(middleware);
await request.post({
uri: '/session',
json: {
openidTokens: {
id_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
}
},
baseUrl, jar
});
});
it('should redirect to postLogoutRedirectUri in auth() config', async function() {
const logoutResponse = await request.get({uri: '/logout', baseUrl, jar, followRedirect: false});
assert.equal(logoutResponse.headers.location, 'https://external-domain.com/after-logout-in-auth-config');
});
it('should redirect to returnTo in logout query', async function() {
const logoutResponse = await request.get({uri: '/logout', qs: {returnTo: 'https://external-domain.com/after-logout-in-logout-query'}, baseUrl, jar, followRedirect: false});
assert.equal(logoutResponse.headers.location, 'https://external-domain.com/after-logout-in-logout-query');
});
});
});
});

Sorry, the diff of this file is not supported yet

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