[!IMPORTANT] 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) and as an OpenID Connect relying party.
It focuses on secure access to OAuth/OIDC tokens and supports authorization code flow for both OAuth 2.0 and OIDC.
mvn clean installmvn testmvn verifymvn install -DskipITsmvn verify -Dit.keycloak.enabled=falsemvn test -Dtest=OidcConnectionImplTestmvn verify -Dit=AuthorizationCodeFlowITsrc/ main/ features/ main.json # Base feature model redis.json # Optional Redis token store feature overlay java/ org/apache/sling/auth/oauth_client/ *.java # Public API impl/ # OSGi DS components (internal) spi/ # Extension points for auth integration support/ # Consumer helper base classes test/ java/ .../itbundle/ # IT-only support bundle generation/install helpers resources/ keycloak-import/ # Local dev / IT realm data
The OAuthTokenAccess OSGi service exposes methods to retrieve and clear access tokens. These methods encapsulate persistence concerns and handle refresh tokens transparently, when present.
@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()) { return doQuery(tokenResponse.getTokenValue()); } return null; } public String getRedirectLink() { if (!tokenResponse.hasValidToken()) { return tokenResponse.getRedirectUri().toString(); } return null; } }
The bundle exposes an abstract OAuthEnabledSlingServlet that handles token retrieval/refresh and redirects to the OAuth/OIDC flow when needed.
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 ClientConnection connection, @Reference OAuthTokenAccess tokenAccess, @Reference MyRemoteService svc) { super(connection, tokenAccess); this.svc = svc; } @Override protected void doGetWithToken( @NotNull SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response, String accessToken) throws IOException, ServletException { this.svc.query("my-query", accessToken).writeResponseTo(response.getOutputStream()); } }
This bundle also ships OidcAuthenticationHandler for Sling Auth Core integration.
Notable capabilities:
redirect request parameter support to return users to a specific local path after authenticationresource) for RFC 8707sling.oauth-request-key cookie (requestKeyCookieMaxAgeSeconds)pkceEnabled)enableSPInitiatedSingleLogout) with host allow-list enforcement (logoutRedirectAllowedHosts)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 is generally recommended because it can return a redirect URI that starts a new OAuth authorization flow.
@Model(adaptables = SlingHttpServletRequest.class) public class MySlingModel { @OSGiService private OAuthTokenAccess tokenAccess; @SlingObject SlingHttpServletRequest request; @OSGiService(filter = "(name=foo)") private ClientConnection connection; public String getLink() { // code elided if (accessTokenIsInvalid()) { OAuthTokenResponse response = tokenAccess.clearAccessToken(connection, request, request.getRequestURI()); return response.getRedirectUri().toString(); } return null; } }
Use this for background or non-interactive invalidation where no redirect URI is needed.
@Component public class MyComponent { @Reference private OAuthTokenAccess tokenAccess; public void execute(@Reference ClientConnection connection, ResourceResolver resolver) { // code elided if (accessTokenIsInvalid()) { tokenAccess.clearAccessToken(connection, resolver); } } }
OAuthEnabledSlingServletFor subclasses of OAuthEnabledSlingServlet, override isInvalidAccessTokenException. If it returns true, the token is cleared and a new OAuth flow starts.
@Component(service = Servlet.class) @SlingServletPaths("/bin/myservlet") public class MySlingServlet extends OAuthEnabledSlingServlet { // other methods elided @Override protected boolean isInvalidAccessTokenException(Exception e) { return e.getCause() instanceof InvalidAccessTokenException; } }
Top-level OAuth servlets validate required parameters and return HTTP 400 for missing/invalid inputs.
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.OAuthCallbackExceptionorg.apache.sling.auth.oauth_client.impl.OAuthEntryPointExceptionorg.apache.sling.auth.oauth_client.impl.OAuthFlowException (superclass)It is recommended to install dedicated error handlers for these exceptions. See Apache Sling error handling documentation.
Client registration is provider-specific. At minimum:
$HOST/system/sling/oauth/callback (for local development typically http://localhost:8080/system/sling/oauth/callback)Validated providers include:
https://accounts.google.comhttps://github.com/login/oauth/authorize and https://github.com/login/oauth/access_tokenhttps://login.microsoftonline.com/$TENANT_ID/v2.0https://ims-na1.adobelogin.com/ims/authorize/v3 and https://ims-na1.adobelogin.com/ims/token/v1Base 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.
Redis support is optional at runtime (redis.clients.jedis is imported as optional), so the bundle also works without Redis on the classpath.
Because OAuth state values are encrypted/signed, CryptoService must be configured:
"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 name is required because this bundle selects the crypto service by that name.
Configure one (or more) client connections:
OidcConnectionImpl)Configure OIDC either with baseUrl metadata discovery or by explicitly setting all endpoints (authorizationEndpoint, tokenEndpoint, userInfoUrl, jwkSetURL, issuer) — but not both.
"org.apache.sling.auth.oauth_client.impl.OidcConnectionImpl~provider": { "name": "provider", "baseUrl": "https://example.com", "clientId": "$[secret:provider/clientId]", "clientSecret": "$[secret:provider/clientSecret]", "scopes": ["openid"], "additionalAuthorizationParameters": ["prompt=consent"] }
For SP-initiated logout, endSessionEndpoint can also be configured (or discovered from OIDC metadata when available).
OAuthConnectionImpl)"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"], "additionalAuthorizationParameters": ["allow_signup=false"] }
Start the OAuth entry-point flow at:
http://localhost:8080/system/sling/oauth/entry-point?c=provider
If you use OidcAuthenticationHandler as your auth mechanism, configure at least:
"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"] }
When enableSPInitiatedSingleLogout=true, logoutRedirectAllowedHosts is mandatory for open-redirect protection.
Tokens can be stored in JCR (under user home) or Redis.
Tokens are stored at oauth-tokens/$PROVIDER_NAME under the user home.
"org.apache.sling.auth.oauth_client.impl.JcrUserHomeOAuthTokenStore": {}
"org.apache.sling.auth.oauth_client.impl.RedisOAuthTokenStore": { "redisUrl": "redis://localhost:6379" }
make keycloak-run-importmvn clean install -DskipITsmvn feature-launcher:start feature-launcher:stop -Dfeature-launcher.waitForInputmake sling-runmake sling-create-configThen:
http://localhost:8081http://localhost:8080http://localhost:8080/system/sling/oauth/entry-point?c=keycloak-devhttp.port) to avoid collisions; do not assume 8080.-Dit.startTimeoutSeconds=60 (default) to control Sling startup wait in ITs.KEYCLOAK_URL instead of starting a Testcontainers Keycloak.-Dit.keycloak.enabled=false.quay.io/keycloak/keycloak:26.4, while make keycloak-run-import uses quay.io/keycloak/keycloak:20.0.3 for local manual setup.The project threat model is documented at https://github.com/apache/sling/blob/master/docs/threat-model.md .