mirror of
https://github.com/nais/wonderwall.git
synced 2026-02-14 17:49:54 +00:00
docs: move to separate directory, major cleanups
This commit is contained in:
7
docs/README.md
Normal file
7
docs/README.md
Normal 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
166
docs/architecture.md
Normal 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://<app>.nav.no, requesting the service https://<app>.<namespace>, 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://<app>.nav.no) --> Service(Service<br>http://<app>.<namespace>) --> 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://<app>.nav.no, requesting the service https://<app>.<namespace>, 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://<app>.nav.no"]
|
||||
Service["Application Service<br>http://<app>.<namespace>"]
|
||||
Wonderwall["Wonderwall SSO Proxy"]
|
||||
|
||||
IngressSSO["Ingress<br>https://sso.nav.no"]
|
||||
ServiceSSO["Service<br>http://wonderwall-sso-server.<namespace>"]
|
||||
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
215
docs/configuration.md
Normal 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
233
docs/endpoints.md
Normal 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
54
docs/sessions.md
Normal 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
56
docs/usage.md
Normal 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.
|
||||
Reference in New Issue
Block a user