Wonderwall (sidecar for authentication)¶
Wonderwall is an application that implements an OpenID Connect (OIDC) Relying Party (client) in a way that makes it easy to plug into Kubernetes as a sidecar.
Abstract
Wonderwall functions as a reverse proxy that intercepts and proxies requests to your application. It provides endpoints to perform logins and logouts for end users, along with session management - so that your application does not have to.
All HTTP requests to the application will be intercepted by Wonderwall, which is attached to your application's pod as a sidecar.
If the user does not have a valid local session with the sidecar, the request will be proxied as-is without modifications to the application container.
In order to obtain a local session, the user must be redirected to the /oauth2/login
endpoint, which performs the
OpenID Connect Authorization Code Flow.
If the user successfully completed the login flow, the sidecar creates and stores a session. A corresponding session
cookie is created and set before finally redirecting user agent to the application. All requests that
are forwarded to the application container will now contain an Authorization
header with the user's access_token
:
Your application is responsible for validating the access_token
.
Availability
This feature is only available in dev-gcp and prod-gcp.
Overview¶
The diagram below shows the overall architecture of an application when using Wonderwall as a sidecar:
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
The sequence diagram below shows the default behavior of the sidecar:
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
Generally speaking, the recommended approach when using the Wonderwall sidecar is to put it in front of your backend-for-frontend server that serves your frontend. Otherwise, you might run into issues with the cookie configuration and allowed redirects - these are both effectively restricted to only match the domain and path for your application's ingress.
Endpoints¶
The sidecar provides these endpoints under your application's ingress:
Path | Description | Note |
---|---|---|
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 | Only for Azure AD |
GET /oauth2/session |
Returns the current user's session metadata | |
POST /oauth2/session/refresh |
Refreshes the tokens and returns the session metadata for the current user | Only for Azure AD |
Usage¶
Overview¶
The contract for usage of the sidecar is fairly simple.
For any endpoint that requires authentication:
- Validate the
Authorization
header as specified in the application guidelines. - If the
Authorization
header is missing, redirect the user to the login endpoint. - If the JWT
access_token
in theAuthorization
header is invalid or expired, redirect the user to the login endpoint. - If you need to log out a user, redirect the user to the logout endpoint.
Providers¶
Refer to the specific identity provider documentation for additional details that apply to the given provider:
1. Initiate Login¶
When you must authenticate a user, redirect to the user to:
The user will be sent to the identity provider for authentication and then back to the sidecar's callback endpoint.
1.1. Redirect after Login¶
After the callback is handled and the user is successfully authenticated, the user will be redirected in this priority:
-
The URL or absolute path set in the query parameter
If you include query parameters, ensure that they are URL encoded. The host and scheme (if provided) are stripped from the redirect URL, which effectively only allows redirects to paths within your own ingress.redirect
in the initial login request, e.g: -
The root context path for your application's ingress. E.g.
/
forhttps://<app>.nav.no
, or/path
forhttps://nav.no/path
.
1.2. Autologin¶
If enabled, the autoLogin
option will configure Wonderwall to automatically redirect HTTP GET
requests to the login
endpoint if the user does not have a session. It will 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 within as specified
in the application guidelines. This is especially important as auto-logins 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 to the login endpoint.
Example configuration:
This will match for all paths for your application's ingresses, except the following:
/oauth2/*
spec.prometheus.path
, if definedspec.liveness.path
, if definedspec.readiness.path
, if defined
You can also define additional paths or patterns to be excluded:
The paths must be absolute paths. The match patterns use glob-style matching.
Example match patterns
/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
- ✅ matches:
/a/*/*
- ✅ matches:
/a/b/c
/a/bee/cee
- ❌ does not match:
/a
/a/b
/a/b/c/d
- ✅ matches:
/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
- ✅ matches:
2. Initiate Logout¶
When you must log a user out, redirect to the user to:
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¶
Limited Availability
This feature is only available for Azure AD
If you only want to perform a local logout for the user, perform a GET
request from the user's browser / user agent:
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. Token Validation¶
Secure your endpoints
Your application is responsible for securing its own endpoints.
- If a request does not contain an
Authorization
header, the request should be considered unauthenticated and access should be denied. - If a request has an
Authorization
header that contains a JWT, the token must be validated before access is granted.
Your application should validate the claims and signature
for the JWT Bearer access_token
attached by the sidecar in the Authorization
header.
Each provider may have some differences in claims and values; see their specific page for details:
4. Resource Requirements¶
The sidecar container is set up with some default resource requirements. This can be customized to your needs. Defaults shown below:
5. Sessions¶
Sessions are stored server-side; we only store a session identifier at the end-user's user agent.
The session lifetime depends on the identity provider:
Identity Provider | Session Lifetime |
---|---|
Azure AD | 10 hours |
ID-porten | 1 hour |
After the session has expired, the user must be redirected to the /oauth2/login
endpoint again.
For convenience, we also offer an endpoint that returns metadata about the user's session as a JSON object at GET /oauth2/session
.
This endpoint will respond with HTTP status codes on errors:
HTTP 401 Unauthorized
- no session cookie or matching session found (e.g. user is not authenticated, or has logged out)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
.
Example
Request:
Response:
Most of these fields should be self-explanatory, but we'll be explicit with their description:
Field | Description |
---|---|
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. |
session.ends_in_seconds |
The number of seconds until the session ends. |
tokens.expire_at |
The timestamp that denotes when the tokens within the session will expire. |
tokens.refreshed_at |
The timestamp that denotes when the tokens within the session was last refreshed. |
tokens.expire_in_seconds |
The number of seconds until the tokens expire. |
5.1. Refresh Tokens¶
Limited Availability
This feature is currently only available for Azure AD
Tokens within the session will usually expire before the session itself. To avoid redirecting end-users to the
/oauth2/login
endpoint whenever the access tokens have expired, we can use refresh tokens to silently get new tokens.
This is enabled by default for applications using Wonderwall with Azure AD.
Tokens will at the earliest be automatically renewed 5 minutes before they expire. If the token already has expired, but the session is still exists and is active, a refresh attempt is automatically triggered as long as the session has not ended. This happens whenever the end-user visits any path that belongs to the application.
If you want to manually trigger token refreshes, you can make use of a new endpoint:
POST /oauth2/session/refresh
- manually refreshes the tokens for the user's session, and returns the metadata like in/oauth2/session
described previously
Example
Request:
Response:
{
"session": {
"created_at": "2022-08-31T06:58:38.724717899Z",
"ends_at": "2022-08-31T16:58:38.724717899Z",
"ends_in_seconds": 14658
},
"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
}
}
Additionally, the metadata object returned by both the /oauth2/session
and /oauth2/session/refresh
endpoints now
contain some new fields in addition to the previous fields:
Field | Description |
---|---|
tokens.next_auto_refresh_in_seconds |
The number of seconds until the earliest time where the tokens will automatically be refreshed. |
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. |
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. In other words, a refresh is only triggered if tokens.refresh_cooldown
is false
.
Responsibilities & Guarantees¶
The sidecar:
- Adds the
Authorization
header with the user's JWT access token to the original request if the user has a valid session. - Proxies the original request unmodified to your application, if the user does not have a valid session.
- Owns the
/oauth2
endpoints defined above and intercepts all HTTP requests to these. They will never be forwarded to your application. - Is safe to enable and use with multiple replicas of your application.
- Stores session data to a highly available Redis service on Aiven.
- Validates the
id_token
acquired from this flow in accordance with the OpenID Connect specifications.
The sidecar does not:
- Secure your application's endpoints in any way.
- Validate the user's
access_token
set in theAuthorization
header. The token may be invalid or expired by the time your application receives it.
Development¶
Wonderwall can be fired up locally if you so desire. See the README on GitHub for an example setup with Docker Compose.
Next Steps¶
The access token that Wonderwall provides should only be accepted and used by your application.
In order to access other applications, you should exchange the token in order to get a new token that is correctly scoped to access a given application.
See the respective identity provider pages for details:
Created: 2022-05-02