Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update default leeway and re-write API documentation #30

Merged
merged 4 commits into from
Oct 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 81 additions & 114 deletions API.md
Original file line number Diff line number Diff line change
@@ -1,150 +1,117 @@
# API

Please see the [Getting Started section of the README](https://github.com/auth0/express-openid-connect#getting-started) for how to apply configuration options.

## openidClient.auth parameters

In general, you won't need to configure this middleware besides the required parameters that can be specified through environment variables.

| Name | Default | Description |
|---------------------|---------------------------------|--------------------------------------------------------------------------------|
| issuerBaseURL | `env.ISSUER_BASE_URL` | The url address for the token issuer. |
| httpOptions | none | Default options used for all HTTP calls made by the library ([possible options](https://github.com/sindresorhus/got/tree/v9.6.0#options)) |
| baseURL | `env.BASE_URL` | The url of the web application where you are installing the router. |
| clientID | `env.CLIENT_ID` | The client id. |
| clientSecret | `env.CLIENT_SECRET` | The client secret, only required for some grants. |
| clockTolerance | `5` | The clock's tolerance in seconds for token verification. |
| getUser | `tokenSet => tokenSet.claims()` | An async function receiving a tokenset and returning the profile for `req.openid.user`. |
| required | `true` | If `true`, requires authentication for all routes (redirects to `loginPath`). Pass a function instead of a boolean to base this on the request. |
| handleUnauthorizedErrors | `true` | Install a middleware that handles Unauthorized/401 errors by triggering the login process. |
| routes | `true` | Installs the `GET /login` and `GET /logout` routes. |
| idpLogout | `false` | Logout the user from the identity provider on logout |
| auth0Logout | `false` | Enable Auth0's non-compliant logout feature, only if Auth0 can be detected and the Auth0 instance does not support OpenID Connect session management. |
| redirectUriPath | `/callback` | Relative path for the callback URL; sent to the authorize endpoint in the `redirectUri` parameter. |
| loginPath | `/login` | Relative path to login. |
| logoutPath | `/logout` | Relative path to logout. |
| authorizationParams | See below | The parameters for the authorization call. |

Default value for `authorizationParams` is:
# Public API

```javascript
{
response_type: 'id_token',
response_mode: 'form_post',
scope: 'openid profile email'
}
```

Commonly used `authorizationParams`:
## Configuration Keys

| Name | Default | Description |
|---------------------|------------------------|--------------------------------------------------------------------------------------------------------------|
| response_type | **Required** | The desired authorization processing flow, including what parameters are returned from the endpoints used. |
| response_mode | `undefined` / optional | The mechanism to be used for returning Authorization Response parameters from the Authorization Endpoint. |
| scope | `openid profile email` | The scope of the access token. |
| audience | `undefined` / optional | The audience for the access token. |
Please see the [Getting Started section of the README](https://github.com/auth0/express-openid-connect#getting-started) for examples of how to apply the configuration options to the `auth()` middleware.

## openidClient.requiresAuth
### Required Keys

The `requiresAuth()` middleware protects specific application routes:
The `auth()` middleware has a few configuration keys that are required for initialization.

```javascript
const { auth, requiresAuth } = require('express-openid-connect');
app.use( auth( { required: false } ) );
app.use( '/admin', requiresAuth() );
```
- **`baseURL`** - The root URL for the application router. This can be set automatically with a `BASE_URL` variable in your environment.
- **`clientID`** - The Client ID for your application. This can be set automatically with a `CLIENT_ID` variable in your environment.
- **`issuerBaseURL`** - The root URL for the token issuer with no trailing slash. In Auth0, this is your Application's **Domain** prepended with `https://`. This can be set automatically with an `ISSUER_BASE_URL` variable in your environment.

If all endpoints require the user to be logged in, the default `auth` middleware protects you from this:
If you are using a response type that includes `code` (typically combined with an `audience` parameter), you will need an additional key:

```javascript
app.use( auth( { required: true } ) );
```
- **`clientSecret`** - The Client ID for your application. This can be set automatically with a `CLIENT_SECRET` variable in your environment.

## Session and Context
### Optional Keys

The middleware stores the [openid-client TokenSet](https://github.com/panva/node-openid-client/blob/master/docs/README.md#tokenset) in the user's session.
Additional configuration keys that can be passed to `auth()` on initialization:

Every `req` object is augmented with the following properties when the request is authenticated
- **`auth0Logout`** - Boolean value to enable Auth0's logout feature. Default is `false`.
- **`authorizationParams`** - Object that describes the authorization server request. [See below](#authorization-params-key) for defaults and more details.
- **`clockTolerance`** - Integer value for the system clock's tolerance (leeway) in seconds for ID token verification. Default is `60`.
- **`getUser`** - Asynchronous function that receives a token set and returns the profile for `req.openid.user`. This runs on each application page load for authenticated users. Default is [here](lib/getUser.js).
- **`errorOnRequiredAuth`** - Boolean value to throw a `Unauthorized 401` error instead of triggering the login process for routes that require authentication. Default is `false`.
- **`httpOptions`** - Default options object used for all HTTP calls made by the library ([possible options](https://github.com/sindresorhus/got/tree/v9.6.0#options)). Default is empty.
- **`idpLogout`** - Boolean value to log the user out from the identity provider on application logout. Requires the issuer to provide a `end_session_endpoint` value. Default is `false`.
- **`loginPath`** - Relative path to application login. Default is `/login`.
- **`logoutPath`** - Relative path to application logout. Default is `/logout`.
- **`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`.
- **`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`.
- **`routes`** - Boolean value to automatically install the login and logout routes. See [the examples](EXAMPLES.md) for more information on how this key is used. Default is `true`.

- `req.openid.user`: contains the user information, use this if you need display an attribute of the user. You can change what's end up here by using the `getUser` parameter of the `auth` middleware.
- `req.openid.tokens`: is the instance of [TokenSet](https://github.com/panva/node-openid-client/blob/master/docs/README.md#tokenset).
- `req.openid.client`: is an instance of te [OpenID Client](https://github.com/panva/node-openid-client/blob/master/docs/README.md#client).
- `req.isAuthenticated()`: returns true if the request is authenticated.
### Authorization Params Key

If the request is not authenticated, `req.openid` is `undefined`.
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:

Every `res` object gets the following methods:
```js
{
response_type: "id_token",
response_mode: "form_post",
scope: "openid profile email"
}
```

- `res.openid.login(params)`: trigger an authentication request from any route. It receives the following parameters:
- `params.returnTo`: The url to return to after authentication. Defaults to the current url for GETs and `baseURL` for other methods.
- `params.authorizationParams`: additional parameters for the authorization call.
- `res.openid.logout(params)`: trigger the openid connect logout if supporter by the issuer.
- `params.returnTo`: The url to return to after sign out. Defaults to the `baseURL` for other methods.
A new object can be passed in to change what is returned from the authorization server depending on your specific scenario.

## Authorization handling
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:

By default the library triggers the login process when authentication is required.
```js
app.use(auth({
authorizationParams: {
response_type: "code",
scope: "openid profile email read:reports",
audience: "https://your-api-identifier"
}
}));
```

An anonymous request to the home page in this case will trigger the login process:
Additional custom parameters can be added as well:

```js
app.use(auth()); // Remember that required is true by default
app.get('/', (req, res) => res.render('home'));
app.use(auth({
authorizationParams: {
// Note: you need to provide required parameters if this object is set.
response_type: "id_token",
response_mode: "form_post",
scope: "openid profile email"

// Additional parameters
acr_value: "tenant:test-tenant",
custom_param: "custom-value"
}
}));
```

The same happens in this case:
## `requiresAuth()`

```js
app.use(auth()); // Remember that required is true by default
app.get('/', requiresAuth(), (req, res) => res.render('home'));
The `requiresAuth()` function is an optional middleware that protects specific application routes when the `required` configuration key is set to `false`:

```javascript
const { auth, requiresAuth } = require('express-openid-connect');
app.use( auth( { required: false } ) );
app.use( '/admin', requiresAuth(), (req, res) => res.render('admin') );
```

If you remove the `auth()` middleware above like this:
Using `requiresAuth()` on its own without initializing `auth()` will throw a `401 Unauthorized` error instead of triggering the login process:

```js
// app.use(auth()); // Remember that required is true by default
// app.use(auth({required: true}));
app.get('/', requiresAuth(), (req, res) => res.render('home'));
```

Instead of triggering the login process we get a 401 Unauthorized error.

It is a best practice to decouple your application logic from this library. If you need to raise a 401 error on your own logic and `requiresAuth` is not enough, you can add the `unauthorizedHandler` from this library:
## Session and Context

```js
const {
auth,
requiresAuth,
unauthorizedHandler
} = require('express-openid-connect');

app.use(auth());

// your routes go here
app.get('/a-route', (req, res, next) => {
if (condition) {
return next(new UnauthorizedError('unauthorized because of xyz'));
}
});
This library adds properties and methods to the request and response objects used within route handling.

//trigger login transactions on 401 errors.
app.use(unauthorizedHandler());
```
### Request

If you need an special logic for handling 401s, including the errors raised by this library, you can set `errorOnRequiredAuth` to `true` like this:
Every request object (typically named `req` in your route handler) is augmented with the following when the request is authenticated. If the request is not authenticated, `req.openid` is `undefined`.

```js
const { auth, requiresAuth } = require('express-openid-connect');
- **`req.openid.user`** - Contains the user information returned from the authorization server. You can change what is provided here by using the `getUser` configuration key.
- **`req.openid.tokens`** - Is the [TokenSet](https://github.com/panva/node-openid-client/blob/master/docs/README.md#tokenset) instance obtained during login.
- **`req.openid.client`** - Is the [OpenID Client](https://github.com/panva/node-openid-client/blob/master/docs/README.md#client) instance that can be used for additional OAuth2 and OpenID calls. See [the examples](EXAMPLES.md) for more information on how this is used.
- **`req.isAuthenticated()`** - Returns true if the request is authenticated.

app.use(auth({ errorOnRequiredAuth: true }));
### Response

// your routes go here
Every response object (typically named `res` in your route handler) is augmented with the following:

//handle unauthorized errors with the unauthorizedHandler or
//with your own middleware like this:
app.use((err, req, res, next) => {
if (err.statusCode === 401) {
return res.openid.login(); //trigger the login process like the `unauthorizedHandler`.
}
next(err);
});
```
- **`res.openid.login({})`** - trigger an authentication request from any route. It receives an object with the following keys:
- `returnTo`: The URL to return to after authentication. Defaults to the current URL for `GET` routes and `baseURL` for other methods.
- `authorizationParams`: Additional parameters for the authorization call.
- **`res.openid.logout({})`** - trigger the openid connect logout if supporter by the issuer. It receives an object with the following key:
- `returnTo`: The URL to return to after signing out at the authorization server. Defaults to the `baseURL`.
72 changes: 42 additions & 30 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ app.use(session({
secret: process.env.COOKIE_SECRET
}));

app.use(auth())
app.use(auth({
required: true
}))

app.use('/', (req, res) => {
res.send(`hello ${req.openid.user.name}`);
Expand All @@ -36,59 +38,69 @@ app.use('/', (req, res) => {
What you get:

- Every route after the `auth()` middleware requires authentication.
- If a user try to access a resource without being authenticated, the application will trigger the authentication process. After completion the user is redirected back to the resource.
- The application creates `GET /login` and `GET /logout` routes for easy linking.
- If a user tries to access a resource without being authenticated, the application will redirect the user to log in. After completion the user is redirected back to the resource.
- The application creates `/login` and `/logout` `GET` routes.

## 2. Route Customization
## 2. Require authentication for specific routes

If you need to customize the routes, you can opt-out from the default routes and handle this manually:
If your application has routes accessible to anonymous users, you can enable authorization per routes:

```js
app.use(auth({ routes: false }));
const { auth, requiresAuth } = require('express-openid-connect');

app.get('/account/login', (req, res) => res.openid.login({ returnTo: '/' }));
app.get('/account/logout', (req, res) => res.openid.logout());
app.use(auth({
required: false
}));

// Anyone can access the homepage
app.use('/', (req, res) => res.render('home'));

// Require routes under the /admin/ prefix to check authentication.
app.use('/admin/users', requiresAuth(), (req, res) => res.render('admin-users'));
app.use('/admin/posts', requiresAuth(), (req, res) => res.render('admin-posts'));
```

... or you can define specific routes for the default handling:
Another way to configure this scenario:

```js
app.use(auth({
redirectUriPath: '/custom-callback-path',
loginPath: '/custom-login-path',
logoutPath: '/custom-logout-path',
const { auth } = require('express-openid-connect');

//initialization
app.use(auth({
required: req => req.originalUrl.startsWith('/admin/')
}));
```

Please note that both of these routes are completely optional and not required. Trying to access any protected resource triggers a redirect directly to Auth0 to login.
app.use('/', (req, res) => res.render('home'));
app.use('/admin/users', (req, res) => res.render('admin-users'));
app.use('/admin/posts', (req, res) => res.render('admin-posts'));
```

## 3. Require auth for specific routes
## 3. Route Customization

If your application has routes accessible to anonymous users, you can enable authorization per routes:
If you need to customize the routes, you can opt-out from the default routes and write your own route handler:

```js
const { auth, requiresAuth } = require('express-openid-connect');

app.use(auth({ required: false }));
app.use(auth({ routes: false }));

// Require every route under the /admin prefix to check authentication.
app.use('/admin', requiresAuth());
app.get('/account/login', (req, res) => res.openid.login({ returnTo: '/' }));
app.get('/account/logout', (req, res) => res.openid.logout());
```

Another way to configure this scenario:
... or you can define specific routes in configuration keys where the default handler will run:

```js
const { auth } = require('express-openid-connect');

//initialization
app.use(auth({
required: req => req.originalUrl.startsWith('/admin')
redirectUriPath: '/custom-callback-path',
loginPath: '/custom-login-path',
logoutPath: '/custom-logout-path',
}));
```

Please note that both of these routes are completely optional and not required. Trying to access any protected resource triggers a redirect directly to Auth0 to login.

## 4. Using refresh tokens

Refresh tokens can requested along with access tokens using the `offline_access` scope during login:
Refresh tokens can be requested along with access tokens using the `offline_access` scope during login:

```js
app.use(auth({
Expand All @@ -105,7 +117,7 @@ On a route that calls an API, check for an expired token and attempt a refresh:

```js
app.get('/route-that-calls-an-api', async (req, res, next) => {

let apiData = {};
let tokenSet = req.openid.tokens;

Expand All @@ -122,7 +134,7 @@ app.get('/route-that-calls-an-api', async (req, res, next) => {

try {
apiData = await request(
process.env.API_URL,
process.env.API_URL,
{
headers: { authorization: `Bearer ${tokenSet.access_token}` },
json: true
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ BASE_URL=https://YOUR_APPLICATION_ROOT_URL
// index.js

app.use(auth({
required: true,
issuerBaseURL: 'https://YOUR_DOMAIN',
baseURL: 'https://YOUR_APPLICATION_ROOT_URL',
clientID: 'YOUR_CLIENT_ID'
Expand Down
2 changes: 1 addition & 1 deletion lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const paramsSchema = Joi.object().keys({
clientSecret: Joi.string().optional(),
idTokenAlg: Joi.string().not('none').optional().default('RS256'),
authorizationParams: Joi.object().optional(),
clockTolerance: Joi.number().optional().default(5),
clockTolerance: Joi.number().optional().default(60),
getUser: Joi.func().optional().default(getUser),
required: Joi.alternatives([ Joi.func(), Joi.boolean()]).optional().default(true),
routes: Joi.boolean().optional().default(true),
Expand Down
Loading