docs: move to separate directory, major cleanups

This commit is contained in:
Trong Huu Nguyen
2023-07-20 10:27:30 +02:00
parent d0c5e91c45
commit 5729f46542
7 changed files with 765 additions and 363 deletions

7
docs/README.md Normal file
View File

@@ -0,0 +1,7 @@
# Table of Contents
- [Architecture](architecture.md)
- [Configuration](configuration.md)
- [Endpoints](endpoints.md)
- [Usage](usage.md)
- [Session Management](sessions.md)

166
docs/architecture.md Normal file
View File

@@ -0,0 +1,166 @@
# Architecture
The diagram below shows the overall architecture of an application when using Wonderwall as a sidecar in Kubernetes:
```mermaid
flowchart TB
accTitle: System Architecture
accDescr: The architectural diagram shows the browser sending a request into the Kubernetes container, requesting the ingress https://&ltapp&gt.nav.no, requesting the service https://&ltapp&gt.&ltnamespace&gt, sending it to the pod, which contains the sidecar. The sidecar sends a proxy request to the app, in addition to triggering and handling the Open ID Connect Auth Code Flow to the identity provider. The identity provider is outside the Kubernetes environment.
idp(Identity Provider)
Browser -- 1. initial request --> k8s
Browser -- 2. redirected by Wonderwall --> idp
idp -- 3. performs OpenID Connect Auth Code flow --> Browser
subgraph k8s [Kubernetes]
direction LR
Ingress(Ingress<br>https://&ltapp&gt.nav.no) --> Service(Service<br>http://&ltapp&gt.&ltnamespace&gt) --> Wonderwall
subgraph Pod
direction TB
Wonderwall -- 4. proxy request with access token --> Application
Application -- 5. return response --> Wonderwall
end
end
```
Note that we do not provide any mechanisms to configure `Services` or inject the sidecar into `Deployments` at this time; this is left as an exercise for the reader.
The sequence diagram below shows the default behavior of Wonderwall:
```mermaid
sequenceDiagram
accTitle: Sequence Diagram
accDescr: The sequence diagram shows the default behaviour of the sidecar, depending on whether the user already has a session or not. If the user does have a session, the sequence is as follows: 1. The user visits a path, that requests the ingress. 2. The request is forwarded to wonderwall 3. Wonderwall checks for a session in session storage. 4. Wonderwall attaches Authorization header and proxies request and sends it to the application. 5. The application returns a response to Wonderwall. 6. Wonderwall returns the response to the user. If the user does not have a session, the sequence is as follows: 1. The user visits a path, that requests the ingress. 2. The request is forwarded to wonderwall 3. Wonderwall checks for a session in session storage. 4. Wonderwall proxies the request as-is and sends it to the application. 5. The application returns a response to Wonderwall. 6. Wonderwall returns the response to the user.
actor User
User->>Ingress: visits /path
Ingress-->>Wonderwall: forwards request
activate Wonderwall
Wonderwall-->>Session Storage: checks for session
alt has session
Session Storage-->>Wonderwall: session found
activate Wonderwall
Wonderwall-->>Application: attaches Authorization header and proxies request
Application-->>Wonderwall: returns response
Wonderwall->>User: returns response
deactivate Wonderwall
else does not have session
Session Storage-->>Wonderwall: no session found
activate Wonderwall
Wonderwall-->>Application: proxies request as-is
Application-->>Wonderwall: returns response
Wonderwall->>User: returns response
deactivate Wonderwall
end
```
# Modes
Wonderwall has two runtime modes, the choice of which depends on your specific setup:
1. The _Standalone mode_ is the default mode and is the most restrictive.
2. The _Single Sign-On (SSO) mode_ is an optional mode where the restrictions are loosened.
## Standalone Mode (Default)
The standalone mode is the default mode for Wonderwall and the most restrictive mode.
It encourages a 1-to-1 mapping for a single identity provider client to a upstream application, where each application has their own identity provider client (i.e. their own set of credentials, their own set of redirect URLs, etc.)
This mode is suitable for organizations seeking to implement zero-trust based token architectures, but requires some maturity in terms of automated provisioning and configuration of identity provider clients.
Restrictions:
- Cookies are set to the match the most specific domain and path (if any) for the configured `ingress`.
- Allowed redirects are also similarly restricted to the same domain and path.
- Users will have separate sessions for each application.
- If using an identity provider with SSO capabilities, this means that the user will see a "redirect blip" when navigating between applications. This may be undesirable in terms of user experience, which is an unfortunate trade-off for increased security.
- If you want sessions to be seamlessly shared between applications on a common domain, use Wonderwall in [SSO mode](#single-sign-on-sso-mode).
Generally speaking, the recommended approach when using Wonderwall in standalone mode is to put it in front of your backend-for-frontend server that serves your frontend.
Requests to other APIs should be done through the backend-for-frontend by reverse-proxying.
This avoids having to configure CORS as well as the restrictions on cookies and allowed redirects mandated by Wonderwall.
See the [configuration](configuration.md#standalone-mode-default) document for configuring the standalone mode.
## Single Sign-On (SSO) Mode
The single sign-on mode is an optional mode where some restrictions are loosened, compared to the standalone mode.
The most notable changes are:
- Session cookies are now set and accessible for the whole SSO (sub-)domain that is configured.
- The [`/oauth2/session`](endpoints.md#oauth2session) and [`/oauth2/session/refresh`](endpoints.md#oauth2sessionrefresh) endpoints are configured to allow CORS from origins matching the SSO (sub-)domain.
- [Automatic token refreshes are unavailable](sessions.md#automatic-vs-manual-refresh).
The SSO mode is mostly just the standalone mode split into two parts; a server part and a proxy part.
It allows a single identity provider client to be used across multiple upstream applications within the same domain.
While you technically can do the same using the standalone mode, that approach has multiple issues:
- Having to distribute and synchronize the private JWK to all deployments.
- Having to manage and register each relying party's callback URL at the identity provider. Some providers also impose a limit for each client.
Using the SSO mode only requires you to register the callback URLs that belong to the SSO server.
The server is also the only part that needs to access the private JWK; the SSO proxies will work without it.
The diagram below shows the overall architecture when deploying Wonderwall in SSO mode:
```mermaid
flowchart BT
accTitle: System Architecture (SSO Mode)
accDescr: The architectural diagram shows the browser sending a request into the Kubernetes container, requesting the ingress https://&ltapp&gt.nav.no, requesting the service https://&ltapp&gt.&ltnamespace&gt, sending it to the pod, which contains the sidecar. The sidecar sends a proxy request to the app, in addition to triggering and handling the Open ID Connect Auth Code Flow to the identity provider. The identity provider is outside the Kubernetes environment.
Browser["Browser (User Agent)"]
idp["Identity Provider"]
Ingress["Application Ingress<br>https://&ltapp&gt.nav.no"]
Service["Application Service<br>http://&ltapp&gt.&ltnamespace&gt"]
Wonderwall["Wonderwall SSO Proxy"]
IngressSSO["Ingress<br>https://sso.nav.no"]
ServiceSSO["Service<br>http://wonderwall-sso-server.&ltnamespace&gt"]
WonderwallSSO["Wonderwall SSO Server"]
Browser -- 1. initial request --> Application
Application -- 2. redirected by Wonderwall SSO Proxy --> ApplicationSSO
ApplicationSSO -- 3. redirected by Wonderwall SSO Server --> idp
idp -- 4. performs OpenID Connect Auth Code flow <--> Browser
idp -- 5. redirect to callback after successful login --> ApplicationSSO
ApplicationSSO -- 6. redirect after successful callback --> Application
Application -- 8. return response --> Browser
subgraph ApplicationSSO["Wonderwall SSO Server"]
direction TB
IngressSSO <--> ServiceSSO <--> WonderwallSSO
end
subgraph Application["Application with SSO Proxy"]
direction TB
Ingress <--> Service <--> Wonderwall
subgraph Pod
Wonderwall -- 7. proxy request with access token <--> ApplicationContainer["Application Container"]
end
end
```
See the [configuration](configuration.md#single-sign-on-sso-mode) document for enabling and configuring the SSO mode.
### SSO Server
The SSO server effectively has the same functionality as the standalone mode and handles the same endpoints, just without the reverse-proxying to an upstream application.
The [`/oauth2/login`](endpoints.md#oauth2login) and the [`/oauth2/logout`](endpoints.md#oauth2logout) endpoints now accept redirect URLs matching any subdomain and path within the configured SSO (sub-)domain.
The SSO server should be deployed separately as its own application, being a central relying party for all proxies that should share the same sessions.
### SSO Proxy
The SSO proxy is effectively a read-only replica version of the standalone mode, providing the reverse-proxy functionality to the upstream application.
All OpenID Connect functionality is delegated to the SSO server by means of reverse-proxying or redirects, which is completely transparent to applications.
This also means that all endpoints are still handled as before.
Applications may thus choose to use either the SSO server or the SSO proxy endpoints, whichever is more convenient.
Bear in mind that the SSO proxy restricts allowed redirects to only relative URLs, as opposed to the SSO server.
The SSO proxy should be deployed as a sidecar, just like the standalone mode for Wonderwall.

215
docs/configuration.md Normal file
View File

@@ -0,0 +1,215 @@
# Configuration
Wonderwall can be configured using either command-line flags or equivalent environment variables (i.e. `-`, `.` -> `_`
and uppercase), with `WONDERWALL_` as prefix. E.g.:
```text
openid.client-id -> WONDERWALL_OPENID_CLIENT_ID
```
The following flags are available:
| Flag | Type | Description | Default Value |
|:----------------------------------|:---------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------|
| `auto-login` | boolean | Automatically redirect all HTTP GET requests to login if the user does not have a valid session for all matching upstream paths. | |
| `auto-login-ignore-paths` | strings | Comma separated list of absolute paths to ignore when `auto-login` is enabled. Supports basic wildcard matching with glob-style asterisks. Invalid patterns are ignored. | |
| `bind-address` | string | Listen address for public connections. | `127.0.0.1:3000` |
| `cookie-prefix` | string | Prefix for cookie names. | `io.nais.wonderwall` |
| `encryption-key` | string | Base64 encoded 256-bit cookie encryption key; must be identical in instances that share session store. | |
| `ingress` | strings | Comma separated list of ingresses used to access the main application. | |
| `log-format` | string | Log format, either `json` or `text`. | `json` |
| `log-level` | string | Logging verbosity level. | `info` |
| `metrics-bind-address` | string | Listen address for metrics only. | `127.0.0.1:3001` |
| `openid.acr-values` | string | Space separated string that configures the default security level (`acr_values`) parameter for authorization requests. | |
| `openid.client-id` | string | Client ID for the OpenID client. | |
| `openid.client-jwk` | string | JWK containing the private key for the OpenID client in string format. | |
| `openid.post-logout-redirect-uri` | string | URI for redirecting the user after successful logout at the Identity Provider. | |
| `openid.provider` | string | Provider configuration to load and use, either `openid`, `azure`, `idporten`. | `openid` |
| `openid.resource-indicator` | string | OAuth2 resource indicator to include in authorization request for acquiring audience-restricted tokens. | |
| `openid.scopes` | strings | List of additional scopes (other than `openid`) that should be used during the login flow. | |
| `openid.ui-locales` | string | Space-separated string that configures the default UI locale (`ui_locales`) parameter for OAuth2 consent screen. | |
| `openid.well-known-url` | string | URI to the well-known OpenID Configuration metadata document. | |
| `redis.address` | string | Address of Redis. An empty value will use in-memory session storage. | |
| `redis.connection-idle-timeout` | int | Idle timeout for Redis connections, in seconds. If non-zero, the value should be less than the client timeout configured at the Redis server. A value of `-1` disables timeout. Default is 30 minutes. | `1800` |
| `redis.password` | string | Password for Redis. | |
| `redis.tls` | boolean | Whether or not to use TLS for connecting to Redis. | `true` |
| `redis.username` | string | Username for Redis. | |
| `session.inactivity` | boolean | Automatically expire user sessions if they have not refreshed their tokens within a given duration. | |
| `session.inactivity-timeout` | duration | Inactivity timeout for user sessions. | `30m` |
| `session.max-lifetime` | duration | Max lifetime for user sessions. | `1h` |
| `session.refresh` | boolean | Enable refresh tokens. In standalone mode, will automatically refresh tokens if they are expired as long as the session is valid (i.e. not exceeding `session.max-lifetime` or `session.inactivity-timeout`). | |
| `sso.domain` | string | The domain that the session cookies should be set for, usually the second-level domain name (e.g. `example.com`). | |
| `sso.enabled` | boolean | Enable single sign-on mode; one server acting as the OIDC Relying Party, and N proxies. The proxies delegate most endpoint operations to the server, and only implements a reverse proxy that reads the user's session data from the shared store. | |
| `sso.mode` | string | The SSO mode for this instance. Must be one of `server` or `proxy`. | `server` |
| `sso.server-default-redirect-url` | string | The URL that the SSO server should redirect to by default if a given redirect query parameter is invalid. | |
| `sso.server-url` | string | The URL used by the proxy to point to the SSO server instance. | |
| `sso.session-cookie-name` | string | Session cookie name. Must be the same across all SSO Servers and Proxies that should share sessions. | |
| `upstream-host` | string | Address of upstream host. | `127.0.0.1:8080` |
Boolean flags are by default set to `false` unless noted otherwise.
String/strings flags are by default empty unless noted otherwise.
Duration flags support [Go duration strings](https://pkg.go.dev/time#ParseDuration), e.g.`10h`, `5m`, `30s`, etc.
## Production Use
The `bind-address` configuration should be set to listen to a public interface, e.g. `0.0.0.0:3000`.
The default value only listens to the loopback interface, i.e. localhost - which makes it unavailable for services outside the Kubernetes Pod.
The `encryption-key` configuration should be set.
Otherwise, a random key will be generated and used - which will not persist between restarts. Sessions will also be rendered invalid as they're unable to be decrypted.
The `redis.address` configuration should be set. Otherwise, an in-memory store is used.
This is especially important if you're running multiple replicas of your application that should share the same sessions.
## Modes
Wonderwall has two runtime modes, a standalone mode and a single sign-on (SSO) mode.
See the [architecture](architecture.md#modes) document for further details.
### Standalone Mode (Default)
The default configuration of Wonderwall will start in [_standalone mode_](architecture.md#standalone-mode-default).
At minimum, the following configuration must be provided when in standalone mode:
- `openid.client-id`
- `openid.client-jwk`
- `openid.well-known-url`
- `ingress`
### Single Sign-On (SSO) Mode
When the `sso.enabled` flag is enabled, Wonderwall will start in [_SSO mode_](architecture.md#single-sign-on-sso-mode).
There are two possible modes when in SSO mode. This is controlled with the `sso.mode` flag; the default value is `server`.
#### SSO Server
When the `sso.enabled` flag is enabled and the `sso.mode` flag is set to `server`, Wonderwall will start in [SSO server mode](architecture.md#sso-server).
At minimum, the following configuration must be provided when in SSO server mode:
- `openid.client-id`
- `openid.client-jwk`
- `openid.well-known-url`
- `ingress`
- `redis.address`
- `sso.domain`
- `sso.session-cookie-name`
- `sso.server-default-redirect-url`
### SSO Proxy
When the `sso.enabled` flag is enabled and the `sso.mode` flag is set to `proxy`, Wonderwall will start in [SSO proxy mode](architecture.md#sso-proxy).
At minimum, the following configuration must be provided when in SSO proxy mode:
- `ingress`
- `redis.address`
- `sso.session-cookie-name`
- `sso.server-url`
## Configuration Flag Details
---
### `auto-login-ignore-paths`
List of paths or patterns to ignore when `auto-login` is enabled.
The paths must be absolute paths. The match patterns use glob-style matching.
<details>
<summary>Example Match Patterns (click to expand)</summary>
- `/allowed` or `/allowed/`
- Trailing slashes in paths and patterns are effectively ignored during matching.
- ✅ matches:
- `/allowed`
- `/allowed/`
- ❌ does not match:
- `/allowed/nope`
- `/allowed/nope/`
- `/public/*`
- A single asterisk after a path means any subpath _directly_ below the path, excluding itself and any nested paths.
- ✅ matches:
- `/public/a`
- ❌ does not match:
- `/public`
- `/public/a/b`
- `/public/**`
- Double asterisks means any subpath below the path, including itself and any nested paths.
- ✅ matches:
- `/public`
- `/public/a`
- `/public/a/b`
- ❌ does not match:
- `/not/public`
- `/not/public/a`
- `/any*`
- ✅ matches:
- `/any`
- `/anything`
- `/anywho`
- ❌ does not match:
- `/any/thing`
- `/anywho/mst/ve`
- `/a/*/*`
- ✅ matches:
- `/a/b/c`
- `/a/bee/cee`
- ❌ does not match:
- `/a`
- `/a/b`
- `/a/b/c/d`
- `/static/**/*.js`
- ✅ matches:
- `/static/bundle.js`
- `/static/min/bundle.js`
- `/static/vendor/min/bundle.js`
- ❌ does not match:
- `/static`
- `/static/some.css`
- `/static/min`
- `/static/min/some.css`
- `/static/vendor/min/some.css`
</details>
---
### `openid.provider`
#### ID-porten
When the `openid.provider` flag is set to `idporten`, the following environment variables are bound to the required `openid`
flags described previously:
- `IDPORTEN_CLIENT_ID`
Client ID for the client at ID-porten.
- `IDPORTEN_CLIENT_JWK`
Private key belonging to the client in JWK format.
- `IDPORTEN_WELL_KNOWN_URL`
Well-known OpenID Configuration endpoint for ID-porten: <https://docs.digdir.no/oidc_func_wellknown.html>.
The default values for the following flags are also changed:
| Flag | Value |
|---------------------|----------|
| `openid.acr-values` | `Level4` |
| `openid.ui-locales` | `nb` |
#### Azure AD
When the `openid.provider` flag is set to `azure`, the following environment variables are bound to the required flags
described previously:
- `AZURE_APP_CLIENT_ID`
Client ID for the client at Azure AD.
- `AZURE_APP_CLIENT_JWK`
Private key belonging to the client in JWK format.
- `AZURE_APP_WELL_KNOWN_URL`
Well-known OpenID Configuration endpoint for Azure AD.

233
docs/endpoints.md Normal file
View File

@@ -0,0 +1,233 @@
# Endpoints
Wonderwall exposes and owns these endpoints (which means they will never be proxied to the upstream application).
## Endpoints for applications
Endpoints that are available for use by applications:
| Path | Description | Notes |
|--------------------------------|----------------------------------------------------------------------|---------------------------------------------------|
| `GET /oauth2/login` | Initiates the OpenID Connect Authorization Code flow | |
| `GET /oauth2/logout` | Performs local logout and redirects the user to global/single-logout | |
| `GET /oauth2/logout/local` | Performs local logout only | Disabled when `openid.provider` is `idporten`. |
| `GET /oauth2/session` | Returns the current user's session metadata | |
| `POST /oauth2/session/refresh` | Refreshes the tokens for the user's session. | Requires the `session.refresh` flag to be enabled |
## Endpoints for Identity Providers
Endpoints that should be registered at and only be triggered by identity providers:
| Path | Description |
|-----------------------------------|--------------------------------------------------------------------------------------------|
| `GET /oauth2/callback` | Handles the callback from the identity provider |
| `GET /oauth2/logout/callback` | Handles the logout callback from the identity provider |
| `GET /oauth2/logout/frontchannel` | Handles global logout request (initiated by identity provider on behalf of another client) |
## Endpoint Details
The `/oauth2/login` and `/oauth2/logout` endpoints respond with HTTP 3xx status codes, as these OpenID Connect flows inherently rely on browser redirects.
As such, the use of these endpoints require that user agents are _redirected_. Using the Fetch API or XHR with these endpoints will fail.
The `/oauth2/login` and `/oauth2/logout` endpoints also accept query parameters at runtime that can override configured defaults.
These can be used to control redirect URLs and some OpenID Connect request parameters, if supported by the identity
provider. As always, query parameter string values should be URL-encoded.
---
### `/oauth2/login`
Redirect the user here to initiate the OpenID Connect Authorization Code flow.
| Query Parameter | Description | Notes |
|-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `redirect` | Where the user will be redirected after successful callback from the Identity Provider. Must be a relative URL with an absolute path, or an absolute URL. | For standalone or SSO proxy mode, this effectively only allows relative URLs. For SSO server mode, the domain must match any subdomain and path within the configured SSO domain. |
| `level` | The `acr_values` parameter for the OpenID Connect authentication request. | Value must be declared as supported by the Identity Provider through the `acr_values_supported` property in the metadata document. |
| `locale` | The `ui_locales` parameter for the OpenID Connect authentication request | Value must be declared as supported by the Identity Provider through the `ui_locales_supported` property in the metadata document. |
The user will be sent to the identity provider for authentication, and then back to the `/oauth2/callback` endpoint.
Following this, the user will be redirected using the following priority:
1. To the URL provided in the `redirect` query parameter in the initial login-request.
2. If the query parameter was not set or invalid, the redirect will point to different places depending on the [runtime mode](configuration.md#modes):
- Standalone: the context root for the matching ingress that received the HTTP request.
- SSO: the default URL configured using the `sso.server-default-redirect-url` flag.
---
### `/oauth2/logout`
Redirect the user here to clear the session along with local cookies, and to initiate the OpenID Connect RP-Initiated Logout flow.
| Query Parameter | Description | Notes |
|-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `redirect` | Where the user will be redirected after successful callback from the Identity Provider. Must be a relative URL with an absolute path, or an absolute URL. | For standalone or SSO proxy mode, this effectively only allows relative URLs. For SSO server mode, the domain must match any subdomain and path within the configured SSO domain. |
The user will be sent to the identity provider for logout, and then back to the `/oauth2/logout/callback` endpoint.
Following this, the user will be redirected using the following priority:
1. To the URL provided in the `redirect` query parameter in the initial logout-request.
2. If the query parameter was not set or invalid, the URL in the `openid.post-logout-redirect-uri` will be used.
3. If the `openid.post-logout-redirect-uri` flag is not set or empty, to the context root for the matching ingress that received the HTTP request.
---
### `/oauth2/logout/local`
Perform a `GET` request from the user agent (e.g. using the Fetch API or XHR) to this endpoint to clear the session along with local cookies.
**This does _not_ perform single sign-out at the identity provider; use the `/oauth2/logout` endpoint instead if you intend to log a user out globally.**
This endpoint only responds with a HTTP 204 No Content on successful local logout.
It may respond with a HTTP 500 if the session could not be cleared.
---
### `/oauth2/session`
Perform a `GET` request from the user agent to receive metadata about the user's session as a JSON object.
This endpoint will respond with the following HTTP status codes on errors:
- `HTTP 401 Unauthorized` - no session cookie or matching session found, or maximum lifetime reached
- `HTTP 500 Internal Server Error` - the session store is unavailable, or Wonderwall wasn't able to process the request
Otherwise, an `HTTP 200 OK` is returned with the metadata with the `application/json` as the `Content-Type`.
Note that this endpoint will still return `HTTP 200 OK` for [_inactive_ sessions](sessions.md#session-inactivity), as long as the session is not [_expired_](sessions.md#session-expiry).
This allows applications to display errors before redirecting the user to login on timeouts.
This also means that you should not use the HTTP response status codes alone as an indication of whether the user is authenticated or not.
#### Request:
```
GET /oauth2/session
```
#### Response:
```
HTTP/2 200 OK
Content-Type: application/json
```
```json
{
"session": {
"created_at": "2022-08-31T06:58:38.724717899Z",
"ends_at": "2022-08-31T16:58:38.724717899Z",
"timeout_at": "0001-01-01T00:00:00Z",
"ends_in_seconds": 14658,
"active": true,
"timeout_in_seconds": -1
},
"tokens": {
"expire_at": "2022-08-31T14:03:47.318251953Z",
"refreshed_at": "2022-08-31T12:53:58.318251953Z",
"expire_in_seconds": 4166
}
}
```
| Field | Description |
|---------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `session.active` | Whether or not the session is marked as active. If `false`, the session cannot be extended and the user must be redirected to login. |
| `session.created_at` | The timestamp that denotes when the session was first created. |
| `session.ends_at` | The timestamp that denotes when the session will end. After this point, the session cannot be extended and the user must be redirected to login. |
| `session.ends_in_seconds` | The number of seconds until `session.ends_at`. |
| `session.timeout_at` | The timestamp that denotes when the session will time out. The zero-value, `0001-01-01T00:00:00Z`, means no timeout. |
| `session.timeout_in_seconds` | The number of seconds until `session.timeout_at`. A value of `-1` means no timeout. |
| `tokens.expire_at` | The timestamp that denotes when the tokens within the session will expire. |
| `tokens.expire_in_seconds` | The number of seconds until `tokens.expire_at`. |
| `tokens.refreshed_at` | The timestamp that denotes when the tokens within the session was last refreshed. |
If the `session.refresh` flag is enabled, the metadata response will contain a few additional fields:
#### Request:
```
GET /oauth2/session
```
#### Response:
```
HTTP/2 200 OK
Content-Type: application/json
```
```json
{
"session": {
...
},
"tokens": {
...
"next_auto_refresh_in_seconds": -1,
"refresh_cooldown": false,
"refresh_cooldown_seconds": 0
}
}
```
(fields shown earlier are omitted from this example for brevity)
| Field | Description |
|---------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `tokens.next_auto_refresh_in_seconds` | The number of seconds until the earliest time where the tokens will automatically be refreshed. A value of -1 means that automatic refreshing is not enabled. |
| `tokens.refresh_cooldown` | A boolean indicating whether or not the refresh operation is on cooldown or not. |
| `tokens.refresh_cooldown_seconds` | The number of seconds until the refresh operation is no longer on cooldown. |
---
### `/oauth2/session/refresh`
This endpoint only exists if the `session.refresh` flag is enabled.
Perform a `POST` request from the user agent to this endpoint to manually refresh the tokens for the user's session.
The endpoint will respond with a `HTTP 401 Unauthorized` if the session is [_inactive_](sessions.md#session-inactivity).
It is otherwise equivalent to [the `/oauth2/session` endpoint](#oauth2session) described previously.
#### Request:
```
POST /oauth2/session/refresh
```
#### Response:
```
HTTP/2 200 OK
Content-Type: application/json
```
```json
{
"session": {
"created_at": "2022-08-31T06:58:38.724717899Z",
"ends_at": "2022-08-31T16:58:38.724717899Z",
"timeout_at": "0001-01-01T00:00:00Z",
"ends_in_seconds": 14658,
"active": true,
"timeout_in_seconds": -1
},
"tokens": {
"expire_at": "2022-08-31T14:03:47.318251953Z",
"refreshed_at": "2022-08-31T12:53:58.318251953Z",
"expire_in_seconds": 4166,
"next_auto_refresh_in_seconds": 3866,
"refresh_cooldown": true,
"refresh_cooldown_seconds": 37
}
}
```
Note that the refresh operation has a default _cooldown_ period of 1 minute, which may be shorter depending on the token lifetime
of the tokens returned by the identity provider.
The cooldown period exists to limit the amount of refresh token requests that we send to the identity provider.
A refresh is only triggered if `tokens.refresh_cooldown` is `false`. Requests to the endpoint are idempotent while the cooldown is active.

54
docs/sessions.md Normal file
View File

@@ -0,0 +1,54 @@
# Session Management
Sessions are stored server-side; we only store a session identifier at the end-user's user agent.
## Session Metadata
User agents can access their own session metadata by using [the `/oauth2/session` endpoint](endpoints.md#oauth2session).
## Session Expiry
Every session has a maximum lifetime.
The lifetime is indicated by the `session.ends_at` and `session.ends_in_seconds` fields in the session metadata.
When the session reaches the maximum lifetime, it is considered to be _expired_ or _ended_, after which the user is essentially unauthenticated.
A new session must be acquired by redirecting the user to [the `/oauth2/login` endpoint](endpoints.md#oauth2login) again.
The maximum lifetime can be configured with the `session.max-lifetime` flag.
## Session Refreshing
The tokens within the session will usually expire before the session itself.
This is indicated by the `tokens.expire_at` and `tokens.expire_in_seconds` fields in the session metadata.
If you've configured a session lifetime that is longer than the token expiry, you'll probably want to _refresh_ the tokens to avoid redirecting end-users to the `/oauth2/login` endpoint whenever the access tokens have expired.
The ability to refresh tokens requires the `session.refresh` flag to be enabled.
### Automatic vs Manual Refresh
The behaviour for refreshing depends on the [runtime mode](configuration.md#modes) for Wonderwall.
In standalone mode, tokens will at the _earliest_ automatically be renewed 5 minutes before they expire.
If the token already _has_ expired, a refresh attempt is still automatically triggered as long as the session itself not has ended or is marked as inactive.
Automatic refreshes happens whenever the end-user visits or requests any path that is proxied to the upstream application.
In SSO mode, tokens are not automatically refreshed, and must be manually refreshed by performing a request to [the `/oauth2/session/refresh` endpoint](endpoints.md#oauth2sessionrefresh).
## Session Inactivity
A session can be marked as _inactive_ before it _expires_ (reaches the maximum lifetime).
This happens if the time since the last _refresh_ exceeds the given _inactivity timeout_.
An inactive session _cannot_ be refreshed; a new session must be acquired by redirecting the user to the `/oauth2/login` endpoint.
This is useful if you want to ensure that an end-user can re-authenticate with the identity provider if they've been gone from an authenticated session for some time.
Inactivity support is enabled with the `session.inactivity` option, which also requires `session.refresh`.
The activity state of the session is indicated by the `session.active` field in the session metadata.
The time until the session will be marked as inactive are indicated by the `session.timeout_at` and `session.timeout_in_seconds` fields in the session metadata.
The timeout is configured with `session.inactivity-timeout`.
If this timeout is shorter than the token expiry, the session metadata fields `tokens.expire_at` and `tokens.expire_in_seconds` will be reduced accordingly to reflect the inactivity timeout.

56
docs/usage.md Normal file
View File

@@ -0,0 +1,56 @@
# Usage
The contract for using Wonderwall is fairly straightforward.
For any endpoint that requires authentication:
1. Validate the `Authorization` header, and any tokens within.
2. If the `Authorization` header is missing, redirect the user to the [login endpoint](#1-login).
3. If the JWT `access_token` in the `Authorization` header is invalid or expired, redirect the user to
the [login endpoint](#1-login).
4. If you need to log out a user, redirect the user to the [logout endpoint](#2-logout).
Note that Wonderwall does not validate the `access_token` that is attached; this is the responsibility of the upstream application.
Wonderwall only validates the `id_token` in accordance with the OpenID Connect Core specifications.
## Scenarios
### 1. Login
When you must authenticate a user, redirect to the user to [the `/oauth2/login` endpoint](endpoints.md#oauth2login).
#### 1.1. Autologin
The `auto-login` option (disabled by default) will configure Wonderwall to automatically redirect any HTTP `GET` requests to the login endpoint if the user does not have a valid session.
It will automatically set the `redirect` parameter for logins to the URL for the original request so that the user is redirected back to their intended location after login.
You should still check the `Authorization` header for a token and validate the token.
This is especially important as auto-login will **NOT** trigger for HTTP requests that are not `GET` requests, such as `POST` or `PUT`.
To ensure smooth end-user experiences whenever their session expires, your application must thus actively validate and
properly handle such requests. For example, your application might respond with an HTTP 401 to allow frontends to
cache or store payloads before redirecting them back to the login endpoint.
### 2. Logout
When you must authenticate a user, redirect to the user to [the `/oauth2/logout` endpoint](endpoints.md#oauth2logout).
The user's session with the sidecar will be cleared, and the user will be redirected to the identity provider for
global/single-logout, if logged in with SSO (single sign-on) at the identity provider.
#### 2.1 Local Logout
If you only want to perform a _local logout_ for the user, perform a `GET` request from the user's browser / user agent to [the `/oauth2/logout/local` endpoint](endpoints.md#oauth2logoutlocal).
This will only clear the user's local session (i.e. remove the cookies) with the sidecar, without performing global logout at the identity provider.
The endpoint responds with a HTTP 204 after successful logout. It will **not** respond with a redirect.
A local logout is useful for scenarios where users frequently switch between multiple accounts.
This means that they do not have to re-enter their credentials (e.g. username, password, 2FA) between each local logout, as they still have an SSO-session logged in with the identity provider.
If the user is using a shared device with other users, only performing a local logout is thus a security risk.
**Ensure you understand the difference in intentions between the two logout endpoints. If you're unsure, use `/oauth2/logout`.**
### 3. Advanced: Session Management
See the [session management](sessions.md) page for details.