OIDC Integration¶
wcs.backend integrates OpenID Connect (OIDC) single sign-on so a Webcloud7 CMS site can
authenticate users against an external OIDC provider (for example Keycloak). The OIDC
protocol handling is provided by the external pas.plugins.oidc package (a hard
dependency declared in setup.py); wcs.backend overrides the callback and logout views
to add token handling, redirect-host validation and optional REST API token issuance.
Architecture Overview¶
OIDC is implemented as a PAS (Pluggable Authentication Service) plugin from
pas.plugins.oidc, installed in the site’s acl_users. wcs.backend replaces two of
the plugin’s browser views with its own implementations (login/oidc.py):
┌──────────────┐ auth request ┌──────────────┐
│ Browser │ ───────────────▶│ OIDC IdP │
│ │ ◀───────────────│ (Keycloak, │
└──────────────┘ code + state │ …) │
│ └──────────────┘
│ /callback?code=…&state=…
▼
┌─────────────────────────┐ ┌─────────────────┐
│ CallbackView │────▶│ Plone PAS │
│ (wcs.backend override) │ │ (acl_users) │
└─────────────────────────┘ └─────────────────┘
│ rememberIdentity
▼
┌─────────────────────────┐
│ User session + │
│ optional API token │
└─────────────────────────┘
The two overridden views (registered on the OIDC plugin, IOIDCPlugin) are:
CallbackView— handles the redirect back from the IdP: token exchange, user info retrieval, identity remembering, and the post-login redirect.LogoutView— builds the end-session request to the IdP and expires the local session and API token cookies.
Connection details (provider URL, client id/secret, scopes, redirect URIs, the
create_restapi_ticket flag, etc.) are configured on the pas.plugins.oidc plugin
itself in acl_users. The settings below are the wcs.backend-specific registry
records that tune the overridden views.
Configuration¶
The following registry records are installed by wcs.backend (profile
registry/oidc.xml). They control the behavior of the overridden callback and logout
views.
Record |
Type |
Default |
Description |
|---|---|---|---|
|
Bool |
|
When |
|
Bool |
|
When |
|
List |
|
Hostnames that are allowed as redirect targets after login and after logout. A |
These records can be edited through the registry control panel
(/@@registry) or shipped via a registry profile.
Login Flow¶
From an integrator’s perspective:
An anonymous visitor is sent to the OIDC plugin’s login view (provided by
pas.plugins.oidc), which redirects to the IdP’s authorization endpoint. The original location is tracked ascame_fromin the session.The user authenticates at the IdP.
The IdP redirects back to the plugin’s
callbackview (overridden bywcs.backend’sCallbackView):the authorization response is parsed and the token is exchanged;
user info is fetched — from the userinfo endpoint with a Bearer token when
use_access_tokenis enabled, otherwise from the standard response;rememberIdentitycreates/updates the Plone user and establishes the session;the browser is redirected back.
The redirect target is the
came_fromURL when its host is inwcs.backend.oidc.allowed_hosts; otherwise the site root. Wheninclude_api_tokenis enabled and the plugin issues a REST ticket, the JWT is appended as?auth_token=…&oidc_login=1.
Logout¶
The overridden logout view:
builds an OIDC
EndSessionRequestto the IdP (usingpost_logout_redirect_uri+client_id, or the deprecatedredirect_uriform when the plugin’suse_deprecated_redirect_uri_for_logoutproperty is set);expires the Plone auth cookie and the
auth_tokencookie locally;redirects to the IdP end-session endpoint.
The post-logout redirect_uri request parameter is honored only when its host is listed
in wcs.backend.oidc.allowed_hosts; otherwise the site root is used.
Identity Provider Setup (Keycloak example)¶
When using Keycloak as the OIDC provider, create an openid-connect client and register
the redirect URIs that match the plugin’s callback and logout views, for example:
Valid redirect URI:
https://your-site.com/acl_users/<plugin-id>/callbackValid post-logout redirect URI:
https://your-site.com(and any host listed inallowed_hosts)
The exact plugin id and view paths come from the installed pas.plugins.oidc plugin;
register that plugin’s redirect URIs in the IdP client.
Force-Login Behavior¶
wcs.backend registers a IBeforeTraverseEvent subscriber on the site root
(login/force_login.py) that rejects anonymous access to most of the site, funnelling
visitors into the configured SSO login. The handler raises Unauthorized for anonymous
users unless one of the following applies:
the request
Acceptheader isapplication/json(REST API requests pass through);the request method is
OPTIONS(CORS preflight);the URL contains an allowed sub-path —
++resource++,++theme++,++plone++static,++unique++,++api++,@@site-logo,@@images,@@download,@@display-file,passwordreset,adminauth, oracl_users(the OIDC plugin views live underacl_users, so the login/callback flow is never blocked);the requested item is an allowed endpoint —
favicon.ico,plonejsi18n,login,login_form,require_login,@@login-help,sitemap.xml,sitemap.xml.zg,ok, or@@register.
Because the plugin’s views (under acl_users) and the login form are explicitly
allow-listed, force-login and OIDC work together: the public site is locked down while
the OIDC handshake and the REST API remain reachable.
Using the API Token after Login¶
When wcs.backend.oidc.include_api_token is enabled (and the OIDC plugin issues a REST
ticket), the post-login redirect carries the JWT in the URL. A frontend can read it and
use it as a Bearer token for subsequent REST API calls:
// After the OIDC redirect lands on your SPA:
const params = new URLSearchParams(window.location.search);
const token = params.get('auth_token');
if (params.get('oidc_login') === '1' && token) {
// Use the token for authenticated REST API requests
const response = await fetch('https://your-site.com/++api++/@navigation', {
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${token}`,
},
});
const data = await response.json();
}