Skip to content
This repository has been archived by the owner on Oct 24, 2024. It is now read-only.

Commit

Permalink
chore(middleware): Adds appBaseUrl, removes redirect_uri (#377)
Browse files Browse the repository at this point in the history
* chore(middleware): Adds appBaseUrl, removes redirect_uri

BREAKING CHANGE:

Configurations now require appBaseUrl.
redirect_uri is deprecated

* fix(test): Updates travis yarn version

* chore(middleware): fixes spacing

* doc(middleware): clean-up upgrade notes
  • Loading branch information
swiftone authored Jan 29, 2019
1 parent b7c1161 commit a999b95
Show file tree
Hide file tree
Showing 13 changed files with 179 additions and 110 deletions.
2 changes: 1 addition & 1 deletion .oidc.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ module.exports = (overrides = {}) => {
const webConstants = {
CLIENT_ID: process.env.WEB_CLIENT_ID || process.env.CLIENT_ID || '{clientId}',
CLIENT_SECRET: process.env.CLIENT_SECRET || '{clientSecret}',
REDIRECT_URI: `${BASE_URI}/authorization-code/callback`,
APP_BASE_URL: BASE_URI,
...defaults
};

Expand Down
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ install:
- yarn install

before_install:
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.9.4
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.13.0
- export PATH="$HOME/.yarn/bin:$PATH"
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
Expand Down
62 changes: 44 additions & 18 deletions packages/oidc-middleware/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* [Getting started](#getting-started)
* [Usage guide](#usage-guide)
* [API reference](#api-reference)
* [Upgrading](#upgrading)
* [Contributing](#contributing)

This package makes it easy to get your users logged in with Okta using OpenId Connect (OIDC). It enables your Express application to participate in the [authorization code flow][auth-code-docs] flow by redirecting the user to Okta for authentication and handling the callback from Okta. Once this flow is complete, a local session is created and the user context is saved for the duration of the session.
Expand All @@ -19,11 +20,12 @@ This package makes it easy to get your users logged in with Okta using OpenId Co

This library uses semantic versioning and follows Okta's [library version policy](https://developer.okta.com/code/library-versions/).

:heavy_check_mark: The current stable major version series is: 1.x
:heavy_check_mark: The current stable major version series is: 2.x

| Version | Status |
| ------- | ------------------------- |
| 1.x | :heavy_check_mark: Stable |
| 2.x | :heavy_check_mark: Stable |
| 1.x | :x: Deprecated |
| 0.x | :x: Retired |

The latest release can always be found on the [releases page][github-releases].
Expand All @@ -37,7 +39,9 @@ If you run into problems using the SDK, you can:

## Getting started

Installing the Okta Node JS OIDC MIddlware in your project is simple.
See [Upgrading](#upgrading) for information on updating to the latest version of the library.

Installing the Okta Node JS OIDC Middlware in your project is simple.

```sh
# npm
Expand Down Expand Up @@ -67,7 +71,7 @@ const oidc = new ExpressOIDC({
issuer: 'https://{yourOktaDomain}/oauth2/default',
client_id: '{clientId}',
client_secret: '{clientSecret}',
redirect_uri: 'http://localhost:3000/authorization-code/callback',
appBaseUrl: '{appBaseUrl}',
scope: 'openid profile'
});

Expand Down Expand Up @@ -106,7 +110,7 @@ oidc.on('error', err => {
* [oidc.router](#oidcrouter)
* [oidc.on('ready', callback)](#oidconready-callback)
* [oidc.on('error', callback)](#oidconerror-callback)
* [oidc.ensureAuthenticated({ redirectTo?: '/uri' })](#oidcensureauthenticated-redirectto-uri)
* [oidc.ensureAuthenticated({ redirectTo?: '/uri' })](#oidcensureauthenticated-redirectto-uri-)
* [req.isAuthenticated()](#reqisauthenticated)
* [req.logout()](#reqlogout)
* [req.userContext](#requsercontext)
Expand All @@ -129,7 +133,7 @@ const oidc = new ExpressOIDC({
issuer: 'https://{yourOktaDomain}/oauth2/default',
client_id: '{clientId}',
client_secret: '{clientSecret}',
redirect_uri: '{redirectUri}',
appBaseUrl: 'https://{yourdomain}',
scope: 'openid profile'
});
```
Expand All @@ -139,10 +143,11 @@ Required config:
* **issuer** - The OIDC provider (e.g. `https://{yourOktaDomain}/oauth2/default`)
* **client_id** - An id provided when you create an OIDC app in your Okta Org
* **client_secret** - A secret provided when you create an OIDC app in your Okta Org
* **redirect_uri** - The callback for your app. Locally, this is usually `http://localhost:3000/authorization-code/callback`. When deployed, this should be `https://{yourProductionDomain}/authorization-code/callback`.
* **appBaseUrl** - The base scheme, host, and port (if not 80/443) of your app, not including any path (e.g. http://localhost:3000, not http://localhost:3000/ ) You may specific `loginRedirectUri` to override this setting if you redirect to other apps.

Optional config:

* **loginRedirectUri** - The URI for your app that Okta will redirect users to after sign in to create the local session. Locally, this is usually `http://localhost:3000/authorization-code/callback`. When deployed, this should be `https://{yourProductionDomain}/authorization-code/callback`. This will default to `{baseUrl}{routes.loginCallback.path}` if `appBaseUrl` is provided, or the (deprecated) `redirect_uri` if appBaseUrl is not provided. Unless your redirect is to a different application, it is recommended to NOT set this parameter and instead set `appBaseUrl` and (if different than the default of `/authorization-code/callback`) `routes.loginCallback.path`.
* **response_type** - Defaults to `code`
* **scope** - Defaults to `openid`, which will only return the `sub` claim. To obtain more information about the user, use `openid profile`. For a list of scopes and claims, please see [Scope-dependent claims](https://developer.okta.com/standards/OIDC/index.html#scope-dependent-claims-not-always-returned) for more information.
* **routes** - Allows customization of the generated routes. See [Customizing Routes](#customizing-routes) for details.
Expand All @@ -163,11 +168,13 @@ const oidc = new ExpressOIDC({ /* options */ });
app.use(oidc.router);
```

It's required in order for `ensureAuthenticated` and `isAuthenticated` to work and adds the following routes:
It's required in order for `ensureAuthenticated`, and `isAuthenticated` to work and adds the following routes:

* `/login` - redirects to the Okta sign-in page by default
* `/authorization-code/callback` - processes the OIDC response, then attaches userinfo to the session

The paths for these generated routes can be customized using the `routes` config, see [Customizing Routes](#customizing-routes) for details.

#### oidc.on('ready', callback)

The middleware must retrieve some information about your client before starting the server. You **must** wait until ExpressOIDC is ready to start your server.
Expand All @@ -180,11 +187,14 @@ oidc.on('ready', () => {

#### oidc.on('error', callback)

This is triggered if an error occurs while ExpressOIDC is trying to start.
This is triggered if an error occurs
* while ExpressOIDC is trying to start
* if an error occurs while calling the Okta `/revoke` service endpoint on the users tokens while logging out
* if the state value for a logout does not match the current session

```javascript
oidc.on('error', err => {
// An error occurred while setting up OIDC
// An error occurred
});
```

Expand All @@ -198,7 +208,7 @@ app.get('/protected', oidc.ensureAuthenticated(), (req, res) => {
});
```

The `redirectTo` option can be used to redirect the user to a specific URI on your site, after a successful authentication callback.
The `redirectTo` option can be used to redirect the user to a specific URI on your site after a successful authentication callback.

#### req.isAuthenticated()

Expand Down Expand Up @@ -265,22 +275,22 @@ const oidc = new ExpressOIDC({
login: {
path: '/different/login'
},
callback: {
loginCallback: {
path: '/different/callback',
handler: (req, res, next) => {
// Perform custom logic before final redirect, then call next()
},
defaultRedirect: '/home'
afterCallback '/home'
}
}
});
```

* **`callback.defaultRedirect`** - Where the user is redirected to after a successful authentication callback, if no `returnTo` value was specified by `oidc.ensureAuthenticated()`. Defaults to `/`.
* **`callback.failureRedirect`** - Where the user is redirected to after authentication failure, defaults to a page which just shows error message.
* **`callback.handler`** - A function that is called after a successful authentication callback, but before the final redirect within your application. Useful for requirements such as conditional post-authentication redirects, or sending data to logging systems.
* **`callback.path`** - The URI that this library will host the callback handler on. Defaults to `/authorization-code/callback`
* **`login.path`** - The URI that redirects the user to the authorize endpoint. Defaults to `/login`.
* **`loginCallback.afterCallback`** - Where the user is redirected to after a successful authentication callback, if no `redirectTo` value was specified by `oidc.ensureAuthenticated()`. Defaults to `/`.
* **`loginCallback.failureRedirect`** - Where the user is redirected to after authentication failure. Defaults to a page which just shows error message.
* **`loginCallback.handler`** - A function that is called after a successful authentication callback, but before the final redirect within your application. Useful for requirements such as conditional post-authentication redirects, or sending data to logging systems.
* **`loginCallback.path`** - The URI that this library will host the login callback handler on. Defaults to `/authorization-code/callback`. Must match a value from the Login Redirect Uri list from the Okta console for this application.
* **`login.path`** - The URI that redirects the user to the Okta authorize endpoint. Defaults to `/login`.

#### Using a Custom Login Page

Expand Down Expand Up @@ -366,6 +376,22 @@ Once you have done that you can read the documentation on the [request][] librar
[openid-client]: https://github.com/panva/node-openid-client
[request]: https://github.com/request/request

### Upgrading

#### from 1.x to 2.x

The 2.x improves support for default options without removing flexibility

Specify the `appBaseUrl` property in your config - this is the base scheme + domain + port for your application that will be used for generating the URIs validated against the Okta settings for your application.

Remove the `redirect_uri` property in your config.
+ * If you are using the Okta default value (appBaseUrl + /authorization-code/callback) it will be given a route by default, no additional configuration required.
+ * If you are NOT using the Okta default value, but are using a route on the same server indicated by your appBaseUrl, you should define your login callback path in your routes.loginCallback.path config (see [the API reference](#expressoidc-api)).

Any customization previously done to `routes.callback` should now be done to `routes.loginCallback` as the name of that property object has changed.

Any value previously set for `routes.callback.defaultRedirect` should now be done to `routes.loginCallback.afterCallback`.

## Contributing

We're happy to accept contributions and PRs! Please see the [contribution guide](https://github.com/okta/okta-oidc-js/blob/master/CONTRIBUTING.md) to understand how to structure a contribution.
4 changes: 2 additions & 2 deletions packages/oidc-middleware/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@okta/oidc-middleware",
"version": "1.0.2",
"version": "2.0.0",
"description": "OpenId Connect middleware for authorization code flows",
"repository": "https://github.com/okta/okta-oidc-js",
"homepage": "https://github.com/okta/okta-oidc-js/tree/master/packages/oidc-middleware",
Expand All @@ -27,7 +27,7 @@
},
"license": "Apache-2.0",
"dependencies": {
"@okta/configuration-validation": "^0.1.1",
"@okta/configuration-validation": "^0.2.0",
"body-parser": "^1.18.2",
"connect-ensure-login": "^0.1.1",
"csurf": "^1.9.0",
Expand Down
42 changes: 26 additions & 16 deletions packages/oidc-middleware/src/ExpressOIDC.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@
*/

const EventEmitter = require('events').EventEmitter;
const _ = require('lodash');
const merge = require('lodash/merge');
const oidcUtil = require('./oidcUtil');
const connectUtil = require('./connectUtil');
const {
assertIssuer,
assertClientId,
assertClientSecret,
assertAppBaseUrl,
assertRedirectUri
} = require('@okta/configuration-validation');

Expand All @@ -32,21 +33,22 @@ module.exports = class ExpressOIDC extends EventEmitter {
* Creates an instance of ExpressOIDC
*
* @param {Object} options
* @param {string} options.appBaseUrl The protocol+domain+port of this app
* @param {string} options.issuer The OpenId Connect issuer
* @param {string} options.client_id This app's OpenId Connect client id
* @param {string} options.client_secret This app's OpenId Connect client secret
* @param {string} options.redirect_uri The location of the authorization callback
* @param {string} options.loginRedirectUri The location of the login authorization callback
* @param {string} [options.scope=openid] The scopes that will determine the claims on the tokens
* @param {string} [options.response_type=code] The OpenId Connect response type
* @param {number} [options.maxClockSkew=120] The maximum discrepancy allowed between server clocks in seconds
* @param {Object} [options.testing] Testing overrides for disabling configuration validation
* @param {Object} [options.routes]
* @param {Object} [options.routes.login]
* @param {string} [options.routes.login.path=/login] Path where the login middleware is hosted
* @param {Object} [options.routes.callback]
* @param {string} [options.routes.callback.path=/authorization-code] Path where the callback middleware is hosted
* @param {string} [options.routes.callback.defaultRedirect=/] Where to redirect if there is no returnTo path defined
* @param {Function} [options.routes.callback.handler] This handles responses from the OpenId Connect callback
* @param {Object} [options.routes.loginCallback
* @param {string} [options.routes.loginCallback.path=/authorization-code] Path where the callback middleware is hosted
* @param {string} [options.routes.loginCallback.afterCallback=/] Where to redirect once callback is complete
* @param {Function} [options.routes.loginCallback.handler] This handles responses from the OpenId Connect callback
*/
constructor(options = {}) {
super();
Expand All @@ -55,7 +57,8 @@ module.exports = class ExpressOIDC extends EventEmitter {
issuer,
client_id,
client_secret,
redirect_uri,
appBaseUrl,
loginRedirectUri,
sessionKey
} = options;

Expand All @@ -68,28 +71,35 @@ module.exports = class ExpressOIDC extends EventEmitter {
// Validate the client_secret param
assertClientSecret(client_secret);

// Validate the redirect_uri param
assertRedirectUri(redirect_uri);
// Validate the appBaseUrl param
assertAppBaseUrl(appBaseUrl);

// Add defaults to the options
options = _.merge({
options = merge({
response_type: 'code',
scope: 'openid',
routes: {
login: {
path: '/login'
},
callback: {
loginCallback: {
path: '/authorization-code/callback',
defaultRedirect: '/'
afterCallback: '/'
}
},
sessionKey: sessionKey || `oidc:${options.issuer}`,
sessionKey: sessionKey || `oidc:${issuer}`,
maxClockSkew: 120
}, options)

// Build redirect uri unless explicitly set
options.loginRedirectUri = loginRedirectUri || `${appBaseUrl}${options.routes.loginCallback.path}`;

// Validate the redirect_uri param
assertRedirectUri(options.loginRedirectUri);

const context = {
options
options,
emitter: this
};

/**
Expand Down Expand Up @@ -117,8 +127,8 @@ module.exports = class ExpressOIDC extends EventEmitter {
.then(client => {
context.client = client;
oidcUtil.bootstrapPassportStrategy(context);
this.emit('ready');
context.emitter.emit('ready');
})
.catch(err => this.emit('error', err));
.catch(err => context.emitter.emit('error', err));
}
};
28 changes: 14 additions & 14 deletions packages/oidc-middleware/src/connectUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,17 @@ const connectUtil = module.exports;

// Create a router to easily add routes
connectUtil.createOIDCRouter = context => {
const routes = context.options.routes;
const oidcRouter = new Router();
oidcRouter.use(passport.initialize({ userProperty: 'userContext' }));
oidcRouter.use(passport.session());

const {
login: {
path:loginPath
},
callback: {
path:callbackPath
}
} = context.options.routes;
const loginPath = routes.login.path;
const loginCallbackPath = routes.loginCallback.path;

oidcRouter.use(loginPath, bodyParser.urlencoded({ extended: false}), connectUtil.createLoginHandler(context));
oidcRouter.use(callbackPath, connectUtil.createCallbackHandler(context));
oidcRouter.use(loginCallbackPath, connectUtil.createLoginCallbackHandler(context));

oidcRouter.use((err, req, res, next) => {
// Cast all errors from the passport strategy as 401 (rather than 500, which would happen if we just call through to next())
res.status(401);
Expand Down Expand Up @@ -63,7 +60,7 @@ connectUtil.createLoginHandler = context => {
nonce,
state,
client_id: context.options.client_id,
redirect_uri: context.options.redirect_uri,
redirect_uri: context.options.loginRedirectUri,
scope: context.options.scope,
response_type: 'code',
sessionToken: req.body.sessionToken
Expand All @@ -80,14 +77,17 @@ connectUtil.createLoginHandler = context => {
}
};

connectUtil.createCallbackHandler = context => {
const customHandler = context.options.routes.callback.handler;
connectUtil.createLoginCallbackHandler = context => {
const routes = context.options.routes;
const customHandler = routes.loginCallback.handler;

if (!customHandler) {
return passport.authenticate('oidc', {
successReturnToOrRedirect: context.options.routes.callback.defaultRedirect,
failureRedirect: context.options.routes.callback.failureRedirect
successReturnToOrRedirect: routes.loginCallback.afterCallback,
failureRedirect: routes.loginCallback.failureRedirect
});
}

const customHandlerArity = customHandler.length;
return (req, res, next) => {
const nextHandler = err => {
Expand Down
6 changes: 3 additions & 3 deletions packages/oidc-middleware/src/oidcUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ oidcUtil.createClient = context => {
issuer,
client_id,
client_secret,
redirect_uri,
loginRedirectUri: redirect_uri,
maxClockSkew,
timeout
} = context.options;
Expand Down Expand Up @@ -87,7 +87,7 @@ oidcUtil.bootstrapPassportStrategy = context => {
passport.serializeUser((user, done) => done(null, user));
passport.deserializeUser((user, done) => done(null, user));
passport.use('oidc', oidcStrategy);
}
};

oidcUtil.ensureAuthenticated = (context, options) => {
options = options || context.options.routes.login.path;
Expand All @@ -102,4 +102,4 @@ oidcUtil.ensureAuthenticated = (context, options) => {
res.sendStatus(401);
}
};
}
};
Loading

0 comments on commit a999b95

Please sign in to comment.