Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"psr/container": "^2.0",
"psr/log": "^3",
"simplesamlphp/composer-module-installer": "^1.3",
"simplesamlphp/openid": "~v0.3.5",
"simplesamlphp/openid": "~0.3.7",
"spomky-labs/base64url": "^2.0",
"symfony/expression-language": "^7.4",
"symfony/psr-http-message-bridge": "^7.4",
Expand Down
79 changes: 79 additions & 0 deletions config/module_oidc.php.dist
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,85 @@ $config = [
*/
ModuleConfig::OPTION_ADMIN_UI_PAGINATION_ITEMS_PER_PAGE => 20,

/***************************************************************************
* (optional) OpenID Connect Dynamic Client Registration (DCR) related
* options. If not enabled (the default), Dynamic Client Registration
* capabilities will be disabled.
**************************************************************************/

/**
* Enable or disable OpenID Connect Dynamic Client Registration (DCR), as
* described in the OpenID Connect Dynamic Client Registration 1.0
* specification (which is also compatible with RFC 7591). Default is
* disabled (false).
*
* When enabled, the module serves:
* - a Client Registration Endpoint (HTTP POST to .../oidc/register) which
* creates a new client from the supplied client metadata and returns its
* client_id, client_secret (for confidential clients), a
* registration_access_token and a registration_client_uri; and
* - a Client Configuration Endpoint (HTTP GET to
* .../oidc/register?client_id=...) which returns the current client
* registration when called with the registration_access_token as an HTTP
* Bearer token.
*
* When enabled, the registration endpoint is also advertised as the
* 'registration_endpoint' claim in the OP discovery metadata.
*
* Note that dynamically registered clients are stored like any other client
* and are visible / manageable in the admin UI.
*/
ModuleConfig::OPTION_DCR_ENABLED => false,

/**
* Access-control mode for the registration (create) endpoint. Only relevant
* if Dynamic Client Registration is enabled. Possible values:
*
* - DcrRegistrationAuthEnum::Open (the default): open registration, meaning
* anyone may register a client without authenticating. In this mode you
* should protect the endpoint from abuse using rate limiting at the
* web-server level.
* - DcrRegistrationAuthEnum::InitialAccessToken: callers must present a
* valid Initial Access Token (provisioned out-of-band) as an HTTP Bearer
* token to register. The accepted tokens are configured using
* the OPTION_DCR_INITIAL_ACCESS_TOKENS option below.
*/
ModuleConfig::OPTION_DCR_REGISTRATION_AUTH =>
\SimpleSAML\Module\oidc\Codebooks\DcrRegistrationAuthEnum::Open->value,

/**
* Allowlist of Initial Access Tokens (opaque, randomly generated strings)
* accepted by the registration endpoint. This option is only consulted when
* the access mode (OPTION_DCR_REGISTRATION_AUTH) is set to
* DcrRegistrationAuthEnum::InitialAccessToken; in 'open' mode it is ignored.
*
* A registration request must then carry one of these tokens as an HTTP
* Bearer token. Use long, high-entropy values and treat them as secrets.
*
* Format: string[] (array of strings)
*/
ModuleConfig::OPTION_DCR_INITIAL_ACCESS_TOKENS => [
// 'a-long-random-secret-token',
],

/**
* Enable or disable impersonation protection for Dynamic Client
* Registration, as recommended by Section 9.1 of the OpenID Connect Dynamic
* Client Registration 1.0 specification. Default is enabled (true).
*
* When enabled, the host component of the logo_uri, policy_uri and tos_uri
* client metadata values (if provided) must match the host of one of the
* registered redirect_uris. Otherwise, the registration is rejected with an
* 'invalid_client_metadata' error. This mitigates a rogue client trying to
* impersonate a legitimate one by reusing its branding (logo) or links.
*
* You may want to disable this (set to false) if your clients legitimately
* host these resources on a different domain than their redirect URIs (for
* example, on a shared CDN or marketing domain). Note that the client_uri
* (the client home page) is intentionally NOT subject to this check.
*/
ModuleConfig::OPTION_DCR_IMPERSONATION_PROTECTION_ENABLED => true,

/***************************************************************************
* (optional) OpenID Federation-related options. If these are not set,
* OpenID Federation capabilities will be disabled.
Expand Down
36 changes: 36 additions & 0 deletions docs/3-oidc-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ It complements the inline comments in `config/module_oidc.php`.
- Attribute translation
- Auth Proc filters (OIDC)
- Client registration permissions
- OpenID Connect Dynamic Client Registration
- Running multiple OPs on one server

## Caching protocol artifacts
Expand Down Expand Up @@ -355,6 +356,41 @@ Users can visit the following link for administration:

- [https://example.com/simplesaml/module.php/oidc/clients/](https://example.com/simplesaml/module.php/oidc/clients/)

## OpenID Connect Dynamic Client Registration

The module can let Relying Parties register themselves dynamically, as described
by [OpenID Connect Dynamic Client Registration 1.0](https://openid.net/specs/openid-connect-registration-1_0.html)
(which is also compatible with RFC 7591). It exposes:

- a **Client Registration Endpoint** (`POST .../oidc/register`) that creates a
client and returns its `client_id`, `client_secret` (for confidential
clients), a `registration_access_token` and a `registration_client_uri`; and
- a **Client Configuration Endpoint** (`GET .../oidc/register?client_id=...`)
that returns the current registration when called with the
`registration_access_token` as a bearer token.

When enabled, the registration endpoint is advertised as `registration_endpoint`
in the OP discovery metadata. Dynamically registered clients are stored like any
other client and are visible in the admin UI.

The feature is **disabled by default**. It is configured through the following
options in `config/module_oidc.php` (see the inline comments there for the full
details and defaults):

- `OPTION_DCR_ENABLED` — master switch for the feature.
- `OPTION_DCR_REGISTRATION_AUTH` — access-control mode: `open` registration
(the default) or `initial_access_token` (require a bearer Initial Access
Token).
- `OPTION_DCR_INITIAL_ACCESS_TOKENS` — the accepted Initial Access Tokens,
consulted only in `initial_access_token` mode.
- `OPTION_DCR_IMPERSONATION_PROTECTION_ENABLED` — when on (the default),
the host of `logo_uri` / `policy_uri` / `tos_uri` must match the host of one of
the registered `redirect_uris` (spec Section 9.1).

> **Security note:** open registration lets anyone create a client, so protect
> the endpoint with rate limiting at the web-server level, or require an Initial
> Access Token.

## Running multiple OPs on one server

A single module instance is designed to serve exactly one OpenID Provider
Expand Down
9 changes: 9 additions & 0 deletions routing/routes/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use SimpleSAML\Module\oidc\Controllers\OAuth2\OAuth2ServerConfigurationController;
use SimpleSAML\Module\oidc\Controllers\OAuth2\TokenIntrospectionController;
use SimpleSAML\Module\oidc\Controllers\PushedAuthorizationController;
use SimpleSAML\Module\oidc\Controllers\RegistrationController;
use SimpleSAML\Module\oidc\Controllers\UserInfoController;
use SimpleSAML\Module\oidc\Controllers\VerifiableCredentials\CredentialIssuerConfigurationController;
use SimpleSAML\Module\oidc\Controllers\VerifiableCredentials\CredentialIssuerCredentialController;
Expand Down Expand Up @@ -105,6 +106,14 @@
$routes->add(RoutesEnum::Jwks->name, RoutesEnum::Jwks->value)
->controller([JwksController::class, 'jwks']);

// OpenID Connect Dynamic Client Registration.
// POST registers a new client (create); GET reads an existing registration
// (Client Configuration Endpoint), authenticated with the Registration
// Access Token.
$routes->add(RoutesEnum::Registration->name, RoutesEnum::Registration->value)
->controller([RegistrationController::class, 'registration'])
->methods([HttpMethodsEnum::GET->value, HttpMethodsEnum::POST->value]);

/*****************************************************************************************************************
* OAuth 2.0 Authorization Server
****************************************************************************************************************/
Expand Down
3 changes: 3 additions & 0 deletions routing/services/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ services:
SimpleSAML\Module\oidc\Server\TokenIssuers\:
resource: '../../src/Server/TokenIssuers/*'

SimpleSAML\Module\oidc\Server\Registration\:
resource: '../../src/Server/Registration/*'

SimpleSAML\Module\oidc\ModuleConfig: ~
SimpleSAML\Module\oidc\Helpers: ~
SimpleSAML\Module\oidc\Forms\Controls\CsrfProtection: ~
Expand Down
24 changes: 24 additions & 0 deletions src/Codebooks/DcrRegistrationAuthEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\Module\oidc\Codebooks;

/**
* Access-control mode for the OIDC Dynamic Client Registration endpoint.
*/
enum DcrRegistrationAuthEnum: string
{
/**
* Open registration: anyone may POST to the registration endpoint (the
* specification's default "open" mode). Relies on deployment-level rate
* limiting to mitigate abuse.
*/
case Open = 'open';

/**
* Registration requires a bearer Initial Access Token, provisioned
* out-of-band (here: a configured static allow-list of tokens).
*/
case InitialAccessToken = 'initial_access_token';
}
2 changes: 2 additions & 0 deletions src/Codebooks/RegistrationTypeEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ enum RegistrationTypeEnum: string
{
case Manual = 'manual';
case FederatedAutomatic = 'federated_automatic';
case Dynamic = 'dynamic';

public function description(): string
{
return match ($this) {
self::Manual => Translate::noop('Manual'),
self::FederatedAutomatic => Translate::noop('Federated Automatic'),
self::Dynamic => Translate::noop('Dynamic'),
};
}
}
2 changes: 2 additions & 0 deletions src/Codebooks/RoutesEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ enum RoutesEnum: string
case UserInfo = 'userinfo';
case Jwks = 'jwks';
case EndSession = 'end-session';
// OpenID Connect Dynamic Client Registration endpoint (create + read).
case Registration = 'register';

/*****************************************************************************************************************
* OAuth 2.0 Authorization Server
Expand Down
Loading
Loading