This is an automated email from the ASF dual-hosted git repository.
cziegeler pushed a commit to branch master
in repository
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-auth-oauth-client.git
The following commit(s) were added to refs/heads/master by this push:
new 80ed4ef docs: rewrite README and AGENTS.md for clarity and
completeness (#48)
80ed4ef is described below
commit 80ed4ef70e05b6f0c3d31df0b29b25e439dbf60c
Author: Carsten Ziegeler <[email protected]>
AuthorDate: Tue Jun 2 09:41:20 2026 +0200
docs: rewrite README and AGENTS.md for clarity and completeness (#48)
Co-authored-by: Maia <maia@noreply>
---
AGENTS.md | 97 +++++++++++++++++
CLAUDE.md | 1 +
README.md | 368 ++++++++++++++++++++++++++++----------------------------------
3 files changed, 265 insertions(+), 201 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..742efef
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,97 @@
+# Project overview
+
+Apache Sling OAuth Client (`org.apache.sling.auth.oauth-client`) is an OSGi
bundle that adds OAuth 2.0 authorization-code flow and OpenID Connect (OIDC)
authentication to Apache Sling. It ships entry-point and callback servlets,
pluggable token stores (in-memory, JCR, Redis), an OIDC authentication handler,
and a public API/SPI for downstream bundles. Packaged with the Sling Feature
Model; targets Sling Starter 13 + Oak 1.40.
+
+# Core commands
+
+```bash
+# Compile and run unit tests
+mvn clean install
+
+# Unit tests only (skip integration tests)
+mvn test
+
+# Full build including integration tests (requires Docker)
+mvn verify
+
+# Skip integration tests explicitly
+mvn install -DskipITs
+
+# Run Sling locally with this bundle (interactive, waits for input to stop)
+mvn feature-launcher:start feature-launcher:stop
-Dfeature-launcher.waitForInput
+
+# Start Keycloak (for local dev/manual testing)
+make keycloak-run-import
+
+# Apply OIDC connection config to running local Sling
+make sling-create-config
+```
+
+Run a single test class:
+```bash
+mvn test -Dtest=OidcConnectionImplTest
+```
+
+Run a single integration test:
+```bash
+mvn verify -Dit=AuthorizationCodeFlowIT
+```
+
+# Project layout
+
+```
+src/
+ main/
+ features/
+ main.json # OSGi feature model (base, includes this bundle)
+ redis.json # Optional Redis token store feature overlay
+ java/
+ org/apache/sling/auth/oauth_client/
+ *.java # Public API: OAuthTokenAccess, ClientConnection,
OAuthTokenResponse
+ impl/ # All OSGi DS components; not part of the public API
+ spi/ # Extension points: UserInfoProcessor,
LoginCookieManager
+ support/ # Base classes for consumers
(OAuthEnabledSlingServlet)
+ test/
+ java/ # Unit and integration tests (mirrors main package
structure)
+ resources/
+ keycloak-import/ # Keycloak realm JSON used by ITs via Testcontainers
+bnd.bnd # BND directives (marks redis.clients.jedis as
optional import)
+pom.xml
+Makefile # Developer convenience targets (keycloak, sling
config)
+```
+
+# Development patterns & constraints
+
+- **Java 11** — `sling.java.version` is set to 11; do not use Java 17+
language features.
+- **OSGi R6/R7 DS annotations** — use `org.osgi.service.component.annotations`
and `org.osgi.service.metatype.annotations`. No Felix SCR annotations.
+- **Bundle manifest** — generated by `bnd-maven-plugin` using `bnd.bnd`; do
not hand-edit `MANIFEST.MF`.
+- **Code formatting** — Spotless is configured; run `mvn spotless:apply`
before committing if the build fails on formatting.
+- **Package visibility** — `impl` classes are not exported. Keep public API in
the root package, SPI in `.spi`, consumer helpers in `.support`.
+- **Redis is optional** — `bnd.bnd` marks `redis.clients.jedis` as
`resolution:=optional`. The bundle must deploy and function without Redis on
the classpath.
+- **4-space indentation**, no trailing whitespace, LF line endings.
+- All source files must carry the Apache License 2.0 header (enforced by
`apache-rat-plugin`).
+
+# Git workflow
+
+- Branch from `master` for features/fixes; use short descriptive branch names.
+- Commit messages: imperative mood, reference JIRA issue if applicable
(`SLING-XXXXX`).
+- PRs are reviewed on GitHub; CI runs via Jenkins (`Jenkinsfile` at root).
+- Include the ASF license header on all new source files.
+
+# Testing guidelines
+
+- **Framework**: JUnit Jupiter (JUnit 5) + AssertJ assertions.
+- **Unit test mocking**: Mockito, Sling Mock (`sling-mock.junit5`), OSGi Mock
(`osgi-mock.junit5`), Sling Mock Oak for JCR-backed tests.
+- **Integration tests** (`*IT.java`): run with `maven-failsafe-plugin`; use
Testcontainers to spin up Keycloak and Redis containers — Docker must be
available.
+- Test files live under `src/test/java/` mirroring the main source package
structure.
+- Coverage is not enforced by a Maven plugin; no coverage thresholds to
maintain.
+- Disable Keycloak ITs: `mvn verify -Dit.keycloak.enabled=false`.
+
+# Gotchas
+
+- **Docker required for ITs** — Testcontainers pulls
`quay.io/keycloak/keycloak` and a Redis image at test time. Without Docker the
`verify` phase fails.
+- **Port reservation** — `build-helper-maven-plugin` reserves a random
`http.port` for the embedded Sling instance during ITs. Do not hard-code port
8080 in tests.
+- **IT startup timeout** — controlled by `-Dit.startTimeoutSeconds=60`
(default). Increase on slow machines.
+- **`org.apache.sling.jcr.resource` is compile-scope** — it is embedded in the
bundle. Changes to that dependency version affect the bundle size and content.
+- **`InMemoryOAuthTokenStore` is not cluster-safe** — use JCR or Redis stores
in any multi-node setup.
+- **Spotless index** — `target/spotless-index` is generated; if the build
fails with a Spotless error, run `mvn spotless:apply` rather than editing
format by hand.
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..9a80b01
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1 @@
+read @AGENTS.md
diff --git a/README.md b/README.md
index 9eb048b..cf8aaca 100644
--- a/README.md
+++ b/README.md
@@ -5,108 +5,127 @@
# Apache Sling OAuth 2.0 client with OIDC support
> [!IMPORTANT]
-> The Java APIs exported by this bundle are considered **experimental** and
are marked as being
-> @ProviderType. The APIs may change in an incompatible way in future minor
releases.
+> The Java APIs exported by this bundle are considered **experimental** and
are marked as
+> `@ProviderType`. The APIs may change in an incompatible way in future minor
releases.
-This bundle adds support for Sling-based applications to function as an OAuth
2.0 client
-([RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749)) and implements the
basis for being an
-[Open ID connect](https://openid.net/developers/how-connect-works/) relying
party.
+This bundle adds support for Sling-based applications to function as an OAuth
2.0 client
+([RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749)) and as an
+[OpenID Connect](https://openid.net/developers/how-connect-works/) relying
party.
-Its main objective is to simplify access to id and access tokens in a secure
manner. It currently supports
-the authentication code flow based on OIDC and OAuth 2.0 .
+It focuses on secure access to OAuth/OIDC tokens and supports authorization
code flow for both OAuth 2.0 and OIDC.
+
+## Build and test
+
+- Build bundle and run tests: `mvn clean install`
+- Unit tests only: `mvn test`
+- Full build including integration tests: `mvn verify`
+- Skip integration tests: `mvn install -DskipITs`
+- Disable Keycloak-based ITs explicitly: `mvn verify
-Dit.keycloak.enabled=false`
## Usage
### Models and other Java APIs
The `OAuthTokenAccess` OSGi service exposes methods to retrieve and clear
access tokens. These methods encapsulate
-persistence concerns and handle refresh tokens transparently, if present.
+persistence concerns and handle refresh tokens transparently, when present.
```java
@Model(adaptables = SlingHttpServletRequest.class)
public class MyModel {
-
+
@SlingObject private SlingHttpServletRequest request;
-
+
@OSGiService(filter = "(name=foo)") private ClientConnection connection;
-
+
@OSGiService private OAuthTokenAccess tokenAccess;
private OAuthTokenResponse tokenResponse;
-
+
@PostConstruct
public void initToken() {
tokenResponse = tokenAccess.getAccessToken(connection, request,
request.getRequestURI());
}
-
+
public MyView getResponse() {
- if ( tokenResponse.hasValidToken() ) {
+ if (tokenResponse.hasValidToken()) {
return doQuery(tokenResponse.getTokenValue());
}
-
return null;
}
-
+
public String getRedirectLink() {
- if ( !tokenResponse.hasValidToken() ) {
+ if (!tokenResponse.hasValidToken()) {
return tokenResponse.getRedirectUri().toString();
}
-
return null;
}
}
-
```
### Servlets
-The bundle exposes an abstract `OAuthEnabledSlingServlet` that contains the
boilerplate code needed
-to obtain a valid OAuth 2 access token.
-
-Basic usage is as follows
+The bundle exposes an abstract `OAuthEnabledSlingServlet` that handles token
retrieval/refresh and redirects to the OAuth/OIDC flow when needed.
```java
-
-import org.apache.sling.auth.oauth_client.*;
-
-@Component(service = { Servlet.class })
-@SlingServletPaths(value = "/bin/myservlet")
+import java.io.IOException;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.auth.oauth_client.ClientConnection;
+import org.apache.sling.auth.oauth_client.OAuthTokenAccess;
+import org.apache.sling.auth.oauth_client.support.OAuthEnabledSlingServlet;
+import org.apache.sling.servlets.annotations.SlingServletPaths;
+import org.jetbrains.annotations.NotNull;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+@Component(service = Servlet.class)
+@SlingServletPaths("/bin/myservlet")
public class MySlingServlet extends OAuthEnabledSlingServlet {
private final MyRemoteService svc;
-
+
@Activate
- public MySlingServlet(@Reference OidcConnection connection,
- @Reference OAuthTokenAccess tokenAccess,
- @Reference MyRemoteService svc) {
+ public MySlingServlet(
+ @Reference ClientConnection connection,
+ @Reference OAuthTokenAccess tokenAccess,
+ @Reference MyRemoteService svc) {
super(connection, tokenAccess);
this.svc = svc;
}
@Override
- protected void doGetWithToken(@NotNull SlingHttpServletRequest request,
@NotNull SlingHttpServletResponse response,
- OAuthToken token) throws IOException, ServletException {
-
- this.svc.query("my-query",
token.getValue()).writeResponseTo(response.getOutputStream());
+ protected void doGetWithToken(
+ @NotNull SlingHttpServletRequest request,
+ @NotNull SlingHttpServletResponse response,
+ String accessToken) throws IOException, ServletException {
+ this.svc.query("my-query",
accessToken).writeResponseTo(response.getOutputStream());
}
}
```
+### OIDC authentication handler
-### Clearing access tokens
+This bundle also ships `OidcAuthenticationHandler` for Sling Auth Core
integration.
+
+Notable capabilities:
-If an access token response contains an expiry date the bundle will make sure
that it is not
-accessible via APIs. This will not cover all scenarios because access tokens
can expire or be
-invalidated out of band.
+- `redirect` request parameter support to return users to a specific local
path after authentication
+- Resource Indicators support (`resource`) for RFC 8707
+- Configurable max age for the transient `sling.oauth-request-key` cookie
(`requestKeyCookieMaxAgeSeconds`)
+- Optional SP-initiated logout support (`enableSPInitiatedSingleLogout`) with
host allow-list enforcement (`logoutRedirectAllowedHosts`)
-The client will need to determine if the access token is invalid as this is a
provider-specific
-check.
+### Clearing access tokens
-#### When the request and response are available
+If an access token response contains an expiry date, the bundle makes sure
expired tokens are not returned by APIs.
+This does not cover out-of-band invalidation at the provider, so clients still
need provider-specific invalid-token handling.
-This method is generally recommended as it permits the generation of a
redirect URI that will kick
-off a new OAuth authorisation flow.
+#### When the request/response are available
+This is generally recommended because it can return a redirect URI that starts
a new OAuth authorization flow.
```java
@Model(adaptables = SlingHttpServletRequest.class)
@@ -114,49 +133,43 @@ public class MySlingModel {
@OSGiService private OAuthTokenAccess tokenAccess;
@SlingObject SlingHttpServletRequest request;
@OSGiService(filter = "(name=foo)") private ClientConnection connection;
-
+
public String getLink() {
// code elided
- if ( accessTokenIsInvalid() ) {
+ if (accessTokenIsInvalid()) {
OAuthTokenResponse response =
tokenAccess.clearAccessToken(connection, request, request.getRequestURI());
return response.getRedirectUri().toString();
}
+ return null;
}
}
```
+#### When request/response are not available
-#### When the request and response are not available
-
-
-This approach should be used when invalidating access tokens without user
interaction, as it does not
-provide a mechanism to generate a redirect URL for restarting the OAuth
authorisation flow and obtaining
-a new access token.
+Use this for background or non-interactive invalidation where no redirect URI
is needed.
```java
@Component
public class MyComponent {
@Reference private OAuthTokenAccess tokenAccess;
-
- public void execute(@Reference OidcConnection connection, ResourceResolver
resolver) {
+
+ public void execute(@Reference ClientConnection connection,
ResourceResolver resolver) {
// code elided
- if ( accessTokenIsInvalid() ) {
+ if (accessTokenIsInvalid()) {
tokenAccess.clearAccessToken(connection, resolver);
}
}
}
```
+#### When extending `OAuthEnabledSlingServlet`
-
-#### When extending OAuthEnabledSlingServlet
-
-For classes that extend from the `OAuthEnabledSlingServlet` the
`isInvalidAccessTokenException` method can be
-overriden. If this method returns true, the access token is cleared and a new
OAuth flow is started.
+For subclasses of `OAuthEnabledSlingServlet`, override
`isInvalidAccessTokenException`. If it returns `true`, the token is cleared and
a new OAuth flow starts.
```java
-@Component(service = { Servlet.class })
-@SlingServletPaths(value = "/bin/myservlet")
+@Component(service = Servlet.class)
+@SlingServletPaths("/bin/myservlet")
public class MySlingServlet extends OAuthEnabledSlingServlet {
// other methods elided
@@ -168,68 +181,67 @@ public class MySlingServlet extends
OAuthEnabledSlingServlet {
}
```
-
### Error handling
-The top-level servlets used for the OAuth flow will validate parameters that
are expected to be
-sent by the client and return a status code of 400 in case the parameters are
missing or invalid.
-
-For others problems related to the OAuth flow these servlets throw specific
subclasses of ServletException.
-The exceptions will return generic messages that can be displayed directly to
the user and store
-the actual cause in nested exception so that it is logged.
+Top-level OAuth servlets validate required parameters and return HTTP 400 for
missing/invalid inputs.
-These exceptions are:
+For other OAuth flow problems, these servlets throw specific
`ServletException` subclasses with user-safe messages and nested root causes
for logging:
- `org.apache.sling.auth.oauth_client.impl.OAuthCallbackException`
- `org.apache.sling.auth.oauth_client.impl.OAuthEntryPointException`
- `org.apache.sling.auth.oauth_client.impl.OAuthFlowException` (superclass)
-It is recommended that applications install specific error handlers for these
exceptions. See the
-[Apache Sling error handling
documentation](https://sling.apache.org/documentation/the-sling-engine/errorhandling.html)
-for more details.
+It is recommended to install dedicated error handlers for these exceptions. See
+[Apache Sling error handling
documentation](https://sling.apache.org/documentation/the-sling-engine/errorhandling.html).
### Client registration
-Client registration is specific to each provider. When registering, note the
following:
+Client registration is provider-specific. At minimum:
-- the redirect URL must be set to $HOST/system/sling/oauth/callback
registered. For development this is typically
http://localhost:8080/system/sling/oauth/callback
-- write down the client id, client secret obtained from the OIDC provider
-- you may need to provide in advance the set of scopes accessible to your
client
+- Register callback URL as `$HOST/system/sling/oauth/callback` (for local
development typically `http://localhost:8080/system/sling/oauth/callback`)
+- Capture provider client ID and client secret
+- Define scopes your application needs
-Validated providers:
+Validated providers include:
-- Google, OIDC, with base URL of https://accounts.google.com , see [Google
OIDC
documentation](https://developers.google.com/identity/protocols/oauth2/openid-connect)
-- GitHub, OAuth 2.0, with authorizationEndpoint
https://github.com/login/oauth/authorize and tokenEndpoint
https://github.com/login/oauth/access_token
-- KeyCloak ( see [Keycloak](#keycloak) )
-- Microsoft, OIDC, with base URL of
https://login.microsoftonline.com/$TENANT\_ID/v2.0. see [Microsoft OIDC
documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc)
-- Adobe IMS, OAuth 2.0, with authorizationEndpoint
https://ims-na1.adobelogin.com/ims/authorize/v3 and tokenEndpoint
https://ims-na1.adobelogin.com/ims/token/v1
+- Google (OIDC) via `https://accounts.google.com`
+- GitHub (OAuth 2.0) via `https://github.com/login/oauth/authorize` and
`https://github.com/login/oauth/access_token`
+- Keycloak
+- Microsoft (OIDC) via `https://login.microsoftonline.com/$TENANT_ID/v2.0`
+- Adobe IMS (OAuth 2.0) via `https://ims-na1.adobelogin.com/ims/authorize/v3`
and `https://ims-na1.adobelogin.com/ims/token/v1`
-### Deployment
+## Deployment
-A set of dependencies required by this bundle, on top of the Sling Starter
ones, is available at `src/main/features/main.json`.
-For the tokens to be stored in Redis ( see [Redis storage](#redis-storage) )
an additional feature with dependencies is found at
`src/main/features/redis.json`.
+Base bundle dependencies (on top of Sling Starter) are defined in
`src/main/features/main.json`.
+Additional dependencies for Redis token storage are in
`src/main/features/redis.json`.
-Since the bundle relies on encryption to create and validate the OAuth 2.0
`state` parameter, a `CryptoService` must be configured
+### CryptoService configuration
+
+Because OAuth state values are encrypted/signed, `CryptoService` must be
configured:
```json
- "org.apache.sling.commons.crypto.internal.FilePasswordProvider~oauth": {
- "path": "secrets/encrypt/password",
- "fix.posixNewline": true
- },
-
"org.apache.sling.commons.crypto.jasypt.internal.JasyptRandomIvGeneratorRegistrar~oauth":
{
- "algorithm": "SHA1PRNG"
- },
-
"org.apache.sling.commons.crypto.jasypt.internal.JasyptStandardPbeStringCryptoService~oauth":
{
- "names": [ "sling-oauth" ],
- "algorithm": "PBEWITHHMACSHA512ANDAES_256"
- }
+"org.apache.sling.commons.crypto.internal.FilePasswordProvider~oauth": {
+ "path": "secrets/encrypt/password",
+ "fix.posixNewline": true
+},
+"org.apache.sling.commons.crypto.jasypt.internal.JasyptRandomIvGeneratorRegistrar~oauth":
{
+ "algorithm": "SHA1PRNG"
+},
+"org.apache.sling.commons.crypto.jasypt.internal.JasyptStandardPbeStringCryptoService~oauth":
{
+ "names": ["sling-oauth"],
+ "algorithm": "PBEWITHHMACSHA512ANDAES_256"
+}
```
-The _sling-oauth_ names property is important since it is used to select the
CryptoService used by this bundle.
+The `sling-oauth` name is required because this bundle selects the crypto
service by that name.
+
+### Client connection configuration
-In addition, one of the following types of OSGi configuration must be added:
+Configure one (or more) client connections:
-#### OIDC variant
+#### OIDC variant (`OidcConnectionImpl`)
+
+You can configure OIDC either with `baseUrl` metadata discovery **or** by
explicitly setting all endpoints (`authorizationEndpoint`, `tokenEndpoint`,
`userInfoUrl`, `jwkSetURL`, `issuer`).
```json
"org.apache.sling.auth.oauth_client.impl.OidcConnectionImpl~provider": {
@@ -237,139 +249,93 @@ In addition, one of the following types of OSGi
configuration must be added:
"baseUrl": "https://example.com",
"clientId": "$[secret:provider/clientId]",
"clientSecret": "$[secret:provider/clientSecret]",
- "scopes": ["openid"]
+ "scopes": ["openid"],
+ "additionalAuthorizationParameters": ["prompt=consent"]
}
```
-#### OAuth variant
+For SP-initiated logout, `endSessionEndpoint` can also be configured (or
discovered from OIDC metadata when available).
+
+#### OAuth variant (`OAuthConnectionImpl`)
```json
-"org.apache.sling.auth.oauth_client.impl.OAuthConnectionImpl~github": {
+"org.apache.sling.auth.oauth_client.impl.OAuthConnectionImpl~provider": {
"name": "provider",
"authorizationEndpoint": "https://example.com/login/oauth/authorize",
"tokenEndpoint": "https://example.com/login/oauth/access_token",
"clientId": "$[secret:provider/clientId]",
"clientSecret": "$[secret:provider/clientSecret]",
- "scopes": ["user:email"]
+ "scopes": ["user:email"],
+ "additionalAuthorizationParameters": ["allow_signup=false"]
}
```
-At this point, the OAuth process can be kicked of by navigating to
http://localhost:8080/system/sling/oauth/entry-point?c=provider
+Start the OAuth entry-point flow at:
-### Token storage
+`http://localhost:8080/system/sling/oauth/entry-point?c=provider`
-The tokens can be stored either in the JCR repository, under the user's home,
or in Redis. A configuration is required to select a provider.
+### OIDC Authentication Handler configuration (optional)
-#### JCR Storage
-
-The tokens are stored under the user's home, under the
`oauth-tokens/$PROVIDER_NAME` node.
-
-```json
-"org.apache.sling.auth.oauth_client.impl.JcrUserHomeOAuthTokenStore" : {
-}
-```
-
-#### Redis storage
+If you use `OidcAuthenticationHandler` as your auth mechanism, configure at
least:
```json
-"org.apache.sling.auth.oauth_client.impl.RedisOAuthTokenStore" : {
- "redisUrl": "redis://localhost:6379"
+"org.apache.sling.auth.oauth_client.impl.OidcAuthenticationHandler~provider": {
+ "path": ["/"],
+ "idp": "oidc",
+ "callbackUri": "http://localhost:8080/system/sling/oauth/callback",
+ "defaultConnectionName": "provider",
+ "pkceEnabled": true,
+ "userInfoEnabled": true,
+ "resource": ["https://api.example.com"],
+ "requestKeyCookieMaxAgeSeconds": 300,
+ "enableSPInitiatedSingleLogout": false,
+ "logoutRedirectPath": "/",
+ "logoutRedirectAllowedHosts": ["localhost"]
}
```
-## Local development setup
-
-### tl;dr
-
-- run the keycloak container using the instructions for 'use existing test
files'
-- build the bundle once with `mvn clean install`
-- run Sling with `mvn feature-launcher:start feature-launcher:stop
-Dfeature-launcher.waitForInput`
-- create OSGi config with
-
-```
-export CLIENT_SECRET=$(cat src/test/resources/keycloak-import/sling.json | jq
--raw-output '.clients[] | select (.clientId == "oidc-test") | .secret')
-
-$ curl -u admin:admin -X POST -d "apply=true" -d
"propertylist=name,baseUrl,clientId,clientSecret,scopes" \
- -d "name=keycloak-dev" \
- -d "baseUrl=http://localhost:8081/realms/sling" \
- -d "clientId=oidc-test"\
- -d "clientSecret=$CLIENT_SECRET" \
- -d "scopes=openid" \
- -d "factoryPid=org.apache.sling.auth.oauth_client.impl.OidcConnectionImpl"
\
-
http://localhost:8080/system/console/configMgr/org.apache.sling.auth.oauth_client.impl.OidcConnectionImpl~keycloak-dev
-```
-
-Now you can
+When `enableSPInitiatedSingleLogout=true`, `logoutRedirectAllowedHosts` is
mandatory for open-redirect protection.
-- access KeyCloak on http://localhost:8081
-- access Sling on http://localhost:8080
-- start the login process on
http://localhost:8080/system/sling/oauth/entry-point?c=keycloak-dev
+## Token storage
-### Keycloak
+Tokens can be stored in JCR (under user home) or Redis.
-#### Use existing test files
+### JCR storage
-Note that this imports the test setup with a single user with a _redirect_uri_
set to _http://localhost*_, which can be a security issue.
-If you plan to export the configuration, store the keycloak database in a
volume. In the following examples we will use the directory `keycloak-data` for
the h2 database
-and for the export directory.
+Tokens are stored at `oauth-tokens/$PROVIDER_NAME` under the user home.
-```
-$ mkdir -p keycloak-data/h2
-$ docker run --rm --volume
$(pwd)/src/test/resources/keycloak-import:/opt/keycloak/data/import --volume
$(pwd)/keycloak-data/h2:/opt/keycloak/data/h2 -p 8081:8080 -e
KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin
quay.io/keycloak/keycloak:20.0.3 start-dev --import-realm
+```json
+"org.apache.sling.auth.oauth_client.impl.JcrUserHomeOAuthTokenStore": {}
```
-#### Manual setup
+### Redis storage
-1. Launch Keycloak locally
-
-```
-$ docker run --rm --volume $(pwd)/keycloak-data:/opt/keycloak/data -p
8081:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin
quay.io/keycloak/keycloak:20.0.3 start-dev
+```json
+"org.apache.sling.auth.oauth_client.impl.RedisOAuthTokenStore": {
+ "redisUrl": "redis://localhost:6379"
+}
```
-2. Create test realm
-
-- access http://localhost:8081/
-- go to 'Administration Console'
-- login with admin:admin
-- open dropdown from the top left and press 'Create realm'
-- Select the name 'sling' and create it
-
-3. Create client
-
-- in the left navigation area, press 'clients'
-- press 'Create client'
-- Fill in 'Client ID' as 'oidc-test' and press 'Next'
-- Enable 'Client authentication' and press 'Save'
-
-4. Configure clients
-
-- in the client details page, set the valid redirect URIs to
http://localhost:8080/system/sling/oauth/callback and save
-- navigate to the 'Credentials' tab and copy the Client secret
-
-5. Add users
+## Local development setup
-- in the left navigation area, press 'users'
-- press 'create new user'
-- fill in username: test and press 'create'
-- go to the 'details' tab, clear any required user actions and press 'save'
-- go to the 'credentials' tab and press 'set password'
-- in the dialog, use 'test' for the password and password confirmation fields
and then press 'save'
-- confirm by pressing 'save password' in the new dialog
+### Quickstart
+1. Start Keycloak with the prebuilt test realm:
+ - `make keycloak-run-import`
+2. Build once:
+ - `mvn clean install -DskipITs`
+3. Start Sling:
+ - `mvn feature-launcher:start feature-launcher:stop
-Dfeature-launcher.waitForInput`
+4. Create OIDC connection config in Sling:
+ - `make sling-create-config`
-### Exporting the test realm
+Then:
-Create a directory to store the exported realm
-```
-mkdir -p $(pwd)/keycloak-data/export
-```
-Export the realm:
-```
-$ docker run --rm --volume
$(pwd)/src/test/resources/keycloak-import:/opt/keycloak/data/import --volume
$(pwd)/keycloak-data/h2:/opt/keycloak/data/h2 --volume
$(pwd)/keycloak-data/export:/opt/keycloak/data/export -p 8082:8080 -e
KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin
quay.io/keycloak/keycloak:20.0.3 export --realm sling --users realm_file --file
/opt/keycloak/data/export/sling.json
-```
+- Keycloak: `http://localhost:8081`
+- Sling: `http://localhost:8080`
+- OAuth entry point:
`http://localhost:8080/system/sling/oauth/entry-point?c=keycloak-dev`
-### Future plans
+### Integration test notes
-- explore an AuthenticationHandler that can optionally expose the access tokens
-- investigate PKCE (RFC 7636)
-- investigate encrypted client-side storage of tokens
+- Integration tests use Testcontainers (Keycloak + Redis) and require Docker.
+- To skip only Keycloak-based integration tests, use
`-Dit.keycloak.enabled=false`.