Skip to content

Commit b69583a

Browse files
authored
Add caching support for protocol artifacts (#270)
* Add cache to client repository * Add cache to access token repository * Add cache to AllowedOriginRepository * Add cache to AuthCodeRepository * Add cache to RefreshTokenRepository * Move PDO logic from entities to repositories * Get rid off RevokeTokenByAuthCodeIdTrait * Add coverage * Update readme
1 parent 3b7d297 commit b69583a

40 files changed

+1381
-288
lines changed

README.md

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Currently supported flows are:
1313

1414
[![Build Status](https://github.com/simplesamlphp/simplesamlphp-module-oidc/actions/workflows/test.yaml/badge.svg)](https://github.com/simplesamlphp/simplesamlphp-module-oidc/actions/workflows/test.yaml)
1515
[![Coverage Status](https://codecov.io/gh/simplesamlphp/simplesamlphp-module-oidc/branch/master/graph/badge.svg)](https://app.codecov.io/gh/simplesamlphp/simplesamlphp-module-oidc)
16-
[![SimpleSAMLphp](https://img.shields.io/badge/simplesamlphp-2.1-brightgreen)](https://simplesamlphp.org/)
16+
[![SimpleSAMLphp](https://img.shields.io/badge/simplesamlphp-2.3-brightgreen)](https://simplesamlphp.org/)
1717

1818
![Main screen capture](docs/oidc.png)
1919

@@ -112,17 +112,27 @@ Once the module is enabled, the database migrations must be run.
112112
### Run database migrations
113113

114114
The module comes with some default SQL migrations which set up needed tables in the configured database. To run them,
115-
go to `OIDC` > `Database Migrations`, and press the available button.
115+
in the SimpleSAMLphp administration area go to `OIDC` > `Database Migrations`, and press the available button.
116116

117117
Alternatively, in case of automatic / scripted deployments, you can run the 'install.php' script from the command line:
118118

119119
php modules/oidc/bin/install.php
120120

121+
### Protocol Artifacts Caching
122+
123+
The configured database serves as the primary storage for protocol artifacts, such as access tokens, authorization
124+
codes, refresh tokens, clients, and user data. In production environments, it is recommended to also set up caching
125+
for these artifacts. The cache layer operates in front of the database, improving performance, particularly during
126+
sudden surges of users attempting to authenticate. The implementation leverages the Symfony Cache component, allowing
127+
the use of any compatible Symfony cache adapter. For more details on configuring the protocol cache, refer to the
128+
module configuration file.
129+
121130
### Relying Party (RP) Administration
122131

123132
The module lets you manage (create, read, update and delete) approved RPs from the module user interface itself.
124133

125-
Once the database schema has been created, you can go to `OIDC` > `Client Registry`.
134+
Once the database schema has been created, in the SimpleSAMLphp administration area go to `OIDC` >
135+
`Client Registry`.
126136

127137
Note that clients can be marked as confidential or public. If the client is not marked as confidential (it is public),
128138
and is using Authorization Code flow, it will have to provide PKCE parameters during the flow.
@@ -136,12 +146,9 @@ to be enabled and configured.
136146

137147
### Endpoint locations
138148

139-
Once you deployed the module, you will need the exact endpoint urls the module provides to configure the relying parties.
140-
You can visit the discovery endpoint to learn this information:
141-
142-
`<basepath>/module.php/oidc/.well-known/openid-configuration`
143-
144-
This endpoint can be used to set up a `.well-known` URL (see below).
149+
Once you deploy the module, in the SimpleSAMLphp administration area go to `OIDC` and then select the
150+
Protocol / Federation Settings page to see the available discovery URLs. These URLs can then be used to set up a
151+
`.well-known` URLs (see below).
145152

146153
### Note when using Apache web server
147154

@@ -161,6 +168,20 @@ SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
161168
```
162169
Choose the one which works for you. If you don't set it, you'll get a warnings about this situation in your logs.
163170

171+
### Note on OpenID Federation (OIDF) support
172+
173+
OpenID Federation support is in "draft" phase, as is the
174+
[specification](https://openid.net/specs/openid-federation-1_0) itself. This means that you can expect braking changes
175+
in future releases related to OIDF capabilities. You can enable / disable OIDF support at any time in module
176+
configuration.
177+
178+
Currently, the following OIDF features are supported:
179+
* endpoint for issuing configuration entity statement (statement about itself)
180+
* fetch endpoint for issuing statements about subordinates (registered clients)
181+
* automatic client registration using a Request Object
182+
183+
OIDF support is implemented using the underlying [SimpleSAMLphp OpenID library](https://github.com/simplesamlphp/openid).
184+
164185
## Additional considerations
165186
### Private scopes
166187

@@ -343,7 +364,7 @@ You may view the OIDC configuration endpoint at `https://localhost/.well-known/o
343364
To test local changes against another DB, such as Postgres, we need to:
344365

345366
* Create a docker network layer
346-
* Run a DB container ( and create a DB if one doesn't exist)
367+
* Run a DB container (and create a DB if one doesn't exist)
347368
* Run SSP and use the DB container
348369

349370
```

UPGRADE.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@
1212
# Version 5 to 6
1313

1414
## New features
15-
15+
- Caching support for OIDC protocol artifacts like Access Tokens, Authorization Codes, Refresh Tokens, but also
16+
client and user data. The cache layer stands in front of the database store, so it can improve performance, especially
17+
in cases of sudden surge of users trying to authenticate. Implementation is based on Symfony Cache component, so any
18+
compatible Symfony cache adapter can be used. Check the module config file for more information on how to set the
19+
protocol cache.
1620
- OpenID capabilities
1721
- New federation endpoints:
1822
- endpoint for issuing configuration entity statement (statement about itself)
@@ -40,7 +44,7 @@ https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication
4044

4145
- (optional) Issuer - you can now override the issuer (OP identifier). If not set, it falls back to current scheme, host
4246
and optionally a port (as in all previous module versions).
43-
- protocol caching adapter and its arguments
47+
- (optional) Protocol caching adapter and its arguments
4448
- (optional) OpenID Federation related options (needed if federation capabilities are to be used):
4549
- enabled or disabled federation capabilities
4650
- valid trust anchors

config-templates/module_oidc.php

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -258,42 +258,48 @@
258258
// also give proper adapter arguments for its instantiation below.
259259
// @see https://symfony.com/doc/current/components/cache.html#available-cache-adapters
260260
ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER => null,
261-
//ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER => \Symfony\Component\Cache\Adapter\FilesystemAdapter::class,
262-
//ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER => \Symfony\Component\Cache\Adapter\MemcachedAdapter::class,
261+
// ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER => \Symfony\Component\Cache\Adapter\FilesystemAdapter::class,
262+
// ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER => \Symfony\Component\Cache\Adapter\MemcachedAdapter::class,
263263

264-
// Federation cache adapter arguments used for adapter instantiation. Refer to documentation for particular
264+
// Protocol cache adapter arguments used for adapter instantiation. Refer to documentation for particular
265265
// adapter on which arguments are needed to create its instance, in the order of constructor arguments.
266266
// See examples below.
267267
ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER_ARGUMENTS => [
268268
// Adapter arguments here...
269269
],
270270
// Example for FileSystemAdapter:
271-
//ModuleConfig::OPTION_FEDERATION_CACHE_ADAPTER_ARGUMENTS => [
272-
// 'openidFederation', // Namespace, subdirectory of main cache directory
273-
// 60 * 60 * 6, // Default lifetime in seconds (used when particular cache item doesn't define its own lifetime)
274-
// '/path/to/main/cache/directory' // Must be writable. Can be set to null to use system temporary directory.
275-
//],
276-
// Example for MemcachedAdapter:
277-
//ModuleConfig::OPTION_FEDERATION_CACHE_ADAPTER_ARGUMENTS => [
278-
// // First argument is a connection instance, so we can use the helper method to create it. In this example a
279-
// // single server is used. Refer to documentation on how to use multiple servers, and / or to provide other
280-
// // options.
281-
// \Symfony\Component\Cache\Adapter\MemcachedAdapter::createConnection(
282-
// 'memcached://localhost'
283-
// // the DSN can include config options (pass them as a query string):
284-
// // 'memcached://localhost:11222?retry_timeout=10'
285-
// // 'memcached://localhost:11222?socket_recv_size=1&socket_send_size=2'
286-
// ),
287-
// 'openidFederation', // Namespace, key prefix.
288-
// 60 * 60 * 6, // Default lifetime in seconds (used when particular cache item doesn't define its own lifetime)
289-
//],
271+
// ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER_ARGUMENTS => [
272+
// 'openidFederation', // Namespace, subdirectory of main cache directory
273+
// 60 * 60 * 6, // Default lifetime in seconds (used when particular cache item doesn't define its own lifetime)
274+
// '/path/to/main/cache/directory' // Must be writable. Can be set to null to use system temporary directory.
275+
// ],
276+
// Example for MemcachedAdapter:
277+
// ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER_ARGUMENTS => [
278+
// // First argument is a connection instance, so we can use the helper method to create it. In this example a
279+
// // single server is used. Refer to documentation on how to use multiple servers, and / or to provide other
280+
// // options.
281+
// \Symfony\Component\Cache\Adapter\MemcachedAdapter::createConnection(
282+
// 'memcached://localhost'
283+
// // the DSN can include config options (pass them as a query string):
284+
// // 'memcached://localhost:11222?retry_timeout=10'
285+
// // 'memcached://localhost:11222?socket_recv_size=1&socket_send_size=2'
286+
// ),
287+
// 'openidProtocol', // Namespace, key prefix.
288+
// 60 * 60 * 6, // Default lifetime in seconds (used when particular cache item doesn't define its own lifetime)
289+
// ],
290290

291+
/**
292+
* Protocol cache duration for particular entities. This is only relevant if protocol cache adapter is set up.
293+
* For duration format info, check https://www.php.net/manual/en/dateinterval.construct.php.
294+
*/
291295
// Cache duration for user entities (authenticated users data). If not set, cache duration will be the same as
292-
// session duration. This is used to avoid fetching user data from database on every authentication event.
293-
// This is only relevant if protocol cache adapter is set up. For duration format info, check
294-
// https://www.php.net/manual/en/dateinterval.construct.php.
296+
// session duration.
295297
// ModuleConfig::OPTION_PROTOCOL_USER_ENTITY_CACHE_DURATION => 'PT1H', // 1 hour
296-
ModuleConfig::OPTION_PROTOCOL_USER_ENTITY_CACHE_DURATION => null, // fallback to session duration
298+
ModuleConfig::OPTION_PROTOCOL_USER_ENTITY_CACHE_DURATION => null, // Fallback to session duration
299+
// Cache duration for client entities, with given default.
300+
ModuleConfig::OPTION_PROTOCOL_CLIENT_ENTITY_CACHE_DURATION => 'PT10M', // 10 minutes
301+
// Cache duration for Authorization Code, Access Token, and Refresh Token will fall back to their TTL.
302+
297303

298304
/**
299305
* Cron related options.

src/Entities/AccessTokenEntity.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
use League\OAuth2\Server\Entities\Traits\AccessTokenTrait;
2525
use League\OAuth2\Server\Entities\Traits\EntityTrait;
2626
use League\OAuth2\Server\Entities\Traits\TokenEntityTrait;
27-
use PDO;
2827
use SimpleSAML\Module\oidc\Entities\Interfaces\AccessTokenEntityInterface;
2928
use SimpleSAML\Module\oidc\Entities\Interfaces\EntityStringRepresentationInterface;
3029
use SimpleSAML\Module\oidc\Entities\Traits\AssociateWithAuthCodeTrait;
@@ -112,7 +111,7 @@ public function getState(): array
112111
'expires_at' => $this->getExpiryDateTime()->format('Y-m-d H:i:s'),
113112
'user_id' => $this->getUserIdentifier(),
114113
'client_id' => $this->getClient()->getIdentifier(),
115-
'is_revoked' => [$this->isRevoked(), PDO::PARAM_BOOL],
114+
'is_revoked' => $this->isRevoked(),
116115
'auth_code_id' => $this->getAuthCodeId(),
117116
'requested_claims' => json_encode($this->requestedClaims, JSON_THROW_ON_ERROR),
118117
];

src/Entities/AuthCodeEntity.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
use League\OAuth2\Server\Entities\ClientEntityInterface as OAuth2ClientEntityInterface;
2020
use League\OAuth2\Server\Entities\Traits\EntityTrait;
2121
use League\OAuth2\Server\Entities\Traits\TokenEntityTrait;
22-
use PDO;
2322
use SimpleSAML\Module\oidc\Entities\Interfaces\AuthCodeEntityInterface;
2423
use SimpleSAML\Module\oidc\Entities\Interfaces\MementoInterface;
2524
use SimpleSAML\Module\oidc\Entities\Traits\OidcAuthCodeTrait;
@@ -66,7 +65,7 @@ public function getState(): array
6665
'expires_at' => $this->getExpiryDateTime()->format('Y-m-d H:i:s'),
6766
'user_id' => $this->getUserIdentifier(),
6867
'client_id' => $this->client->getIdentifier(),
69-
'is_revoked' => [$this->isRevoked(), PDO::PARAM_BOOL],
68+
'is_revoked' => $this->isRevoked(),
7069
'redirect_uri' => $this->getRedirectUri(),
7170
'nonce' => $this->getNonce(),
7271
];

src/Entities/ClientEntity.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
use DateTimeImmutable;
2020
use League\OAuth2\Server\Entities\Traits\ClientTrait;
2121
use League\OAuth2\Server\Entities\Traits\EntityTrait;
22-
use PDO;
2322
use SimpleSAML\Module\oidc\Codebooks\RegistrationTypeEnum;
2423
use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface;
2524
use SimpleSAML\OpenID\Codebooks\ClientRegistrationTypesEnum;
@@ -167,8 +166,8 @@ public function getState(): array
167166
self::KEY_AUTH_SOURCE => $this->getAuthSourceId(),
168167
self::KEY_REDIRECT_URI => json_encode($this->getRedirectUri(), JSON_THROW_ON_ERROR),
169168
self::KEY_SCOPES => json_encode($this->getScopes(), JSON_THROW_ON_ERROR),
170-
self::KEY_IS_ENABLED => [$this->isEnabled(), PDO::PARAM_BOOL],
171-
self::KEY_IS_CONFIDENTIAL => [$this->isConfidential(), PDO::PARAM_BOOL],
169+
self::KEY_IS_ENABLED => $this->isEnabled(),
170+
self::KEY_IS_CONFIDENTIAL => $this->isConfidential(),
172171
self::KEY_OWNER => $this->getOwner(),
173172
self::KEY_POST_LOGOUT_REDIRECT_URI => json_encode($this->getPostLogoutRedirectUri(), JSON_THROW_ON_ERROR),
174173
self::KEY_BACKCHANNEL_LOGOUT_URI => $this->getBackChannelLogoutUri(),
@@ -188,7 +187,7 @@ public function getState(): array
188187
self::KEY_UPDATED_AT => $this->getUpdatedAt()?->format('Y-m-d H:i:s'),
189188
self::KEY_CREATED_AT => $this->getCreatedAt()?->format('Y-m-d H:i:s'),
190189
self::KEY_EXPIRES_AT => $this->getExpiresAt()?->format('Y-m-d H:i:s'),
191-
self::KEY_IS_FEDERATED => [$this->isFederated(), PDO::PARAM_BOOL],
190+
self::KEY_IS_FEDERATED => $this->isFederated(),
192191
];
193192
}
194193

src/Entities/RefreshTokenEntity.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
use DateTimeImmutable;
2020
use League\OAuth2\Server\Entities\Traits\EntityTrait;
2121
use League\OAuth2\Server\Entities\Traits\RefreshTokenTrait;
22-
use PDO;
2322
use SimpleSAML\Module\oidc\Entities\Interfaces\AccessTokenEntityInterface;
2423
use SimpleSAML\Module\oidc\Entities\Interfaces\RefreshTokenEntityInterface;
2524
use SimpleSAML\Module\oidc\Entities\Traits\AssociateWithAuthCodeTrait;
@@ -52,7 +51,7 @@ public function getState(): array
5251
'id' => $this->getIdentifier(),
5352
'expires_at' => $this->getExpiryDateTime()->format('Y-m-d H:i:s'),
5453
'access_token_id' => $this->getAccessToken()->getIdentifier(),
55-
'is_revoked' => [$this->isRevoked(), PDO::PARAM_BOOL],
54+
'is_revoked' => $this->isRevoked(),
5655
'auth_code_id' => $this->getAuthCodeId(),
5756
];
5857
}

src/Helpers/Client.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public function getFromRequest(
3030
$clientId = empty($params['client_id']) ? null : (string)$params['client_id'];
3131

3232
if (!is_string($clientId)) {
33-
throw new BadRequest('Client id is missing.');
33+
throw new BadRequest('Client ID is missing.');
3434
}
3535

3636
$client = $clientRepository->findById($clientId);

src/Helpers/Random.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ public function getIdentifier(int $length = 40): string
2020

2121
try {
2222
return bin2hex(random_bytes($length));
23+
// @codeCoverageIgnoreStart
2324
} catch (Throwable $e) {
2425
throw OidcServerException::serverError('Could not generate a random string', $e);
2526
}
27+
// @codeCoverageIgnoreEnd
2628
}
2729
}

src/ModuleConfig.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class ModuleConfig
8080
final public const OPTION_PROTOCOL_CACHE_ADAPTER = 'protocol_cache_adapter';
8181
final public const OPTION_PROTOCOL_CACHE_ADAPTER_ARGUMENTS = 'protocol_cache_adapter_arguments';
8282
final public const OPTION_PROTOCOL_USER_ENTITY_CACHE_DURATION = 'protocol_user_entity_cache_duration';
83+
final public const OPTION_PROTOCOL_CLIENT_ENTITY_CACHE_DURATION = 'protocol_client_entity_cache_duration';
8384
final public const OPTION_FEDERATION_PARTICIPATION_LIMIT_BY_TRUST_MARKS =
8485
'federation_participation_limit_by_trust_marks';
8586

@@ -464,6 +465,21 @@ public function getProtocolUserEntityCacheDuration(): DateInterval
464465
);
465466
}
466467

468+
/**
469+
* Get cache duration for client entities (user data), with given default
470+
*
471+
* @throws \Exception
472+
*/
473+
public function getProtocolClientEntityCacheDuration(): DateInterval
474+
{
475+
return new DateInterval(
476+
$this->config()->getOptionalString(
477+
self::OPTION_PROTOCOL_CLIENT_ENTITY_CACHE_DURATION,
478+
null,
479+
) ?? 'PT10M',
480+
);
481+
}
482+
467483

468484
/*****************************************************************************************************************
469485
* OpenID Federation related config.

0 commit comments

Comments
 (0)