{"id":22563642,"url":"https://github.com/apache/sling-org-apache-sling-auth-oauth-client","last_synced_at":"2026-01-20T18:09:00.783Z","repository":{"id":266688157,"uuid":"899044240","full_name":"apache/sling-org-apache-sling-auth-oauth-client","owner":"apache","description":"Apache Sling OAuth client","archived":false,"fork":false,"pushed_at":"2025-11-27T16:59:48.000Z","size":460,"stargazers_count":0,"open_issues_count":1,"forks_count":3,"subscribers_count":22,"default_branch":"master","last_synced_at":"2025-11-30T09:19:12.186Z","etag":null,"topics":["java","oauth","oidc","sling"],"latest_commit_sha":null,"homepage":"https://sling.apache.org/","language":"Java","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/apache.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-12-05T14:17:10.000Z","updated_at":"2025-11-27T16:59:52.000Z","dependencies_parsed_at":"2024-12-20T15:41:17.631Z","dependency_job_id":"22224a52-01e0-4661-b07d-c02469e53f3d","html_url":"https://github.com/apache/sling-org-apache-sling-auth-oauth-client","commit_stats":null,"previous_names":["apache/sling-org-apache-sling-auth-oauth-client"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/apache/sling-org-apache-sling-auth-oauth-client","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apache%2Fsling-org-apache-sling-auth-oauth-client","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apache%2Fsling-org-apache-sling-auth-oauth-client/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apache%2Fsling-org-apache-sling-auth-oauth-client/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apache%2Fsling-org-apache-sling-auth-oauth-client/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/apache","download_url":"https://codeload.github.com/apache/sling-org-apache-sling-auth-oauth-client/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apache%2Fsling-org-apache-sling-auth-oauth-client/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28608131,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-20T16:10:39.856Z","status":"ssl_error","status_checked_at":"2026-01-20T16:10:39.493Z","response_time":117,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["java","oauth","oidc","sling"],"created_at":"2024-12-07T23:12:15.461Z","updated_at":"2026-01-20T18:09:00.777Z","avatar_url":"https://github.com/apache.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Apache Sling](https://sling.apache.org/res/logos/sling.png)](https://sling.apache.org)\n\n\u0026#32;[![Build Status](https://ci-builds.apache.org/job/Sling/job/modules/job/sling-org-apache-sling-auth-oauth-client/job/master/badge/icon)](https://ci-builds.apache.org/job/Sling/job/modules/job/sling-org-apache-sling-auth-oauth-client/job/master/)\u0026#32;[![Test Status](https://img.shields.io/jenkins/tests.svg?jobUrl=https://ci-builds.apache.org/job/Sling/job/modules/job/sling-org-apache-sling-auth-oauth-client/job/master/)](https://ci-builds.apache.org/job/Sling/job/modules/job/sling-org-apache-sling-auth-oauth-client/job/master/test/?width=800\u0026height=600)\u0026#32;[![Sonarcloud Status](https://sonarcloud.io/api/project_badges/measure?project=apache_sling-org-apache-sling-auth-oauth-client\u0026metric=alert_status)](https://sonarcloud.io/dashboard?id=apache_sling-org-apache-sling-auth-oauth-client)\u0026#32;[![auth](https://sling.apache.org/badges/group-auth.svg)](https://github.com/apache/sling-aggregator/blob/master/docs/groups/auth.md) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)\n\n# Apache Sling OAuth 2.0 client with OIDC support\n\n\u003e [!IMPORTANT]\n\u003e The Java APIs exported by this bundle are considered **experimental** and are marked as being\n\u003e @ProviderType. The APIs may change in an incompatible way in future minor releases.\n\nThis bundle adds support for Sling-based applications to function as an OAuth 2.0 client \n([RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749)) and implements the basis for being an \n[Open ID connect](https://openid.net/developers/how-connect-works/) relying party.\n\nIts main objective is to simplify access to id and access tokens in a secure manner. It currently supports\nthe authentication code flow based on OIDC and OAuth 2.0 .\n\n## Usage\n\n### Models and other Java APIs\n\nThe `OAuthTokenAccess` OSGi service exposes methods to retrieve and clear access tokens. These methods encapsulate\npersistence concerns and handle refresh tokens transparently, if present.\n\n```java\n@Model(adaptables = SlingHttpServletRequest.class)\npublic class MyModel {\n    \n    @SlingObject private SlingHttpServletRequest request;\n    \n    @OSGiService(filter = \"(name=foo)\") private ClientConnection connection;\n    \n    @OSGiService private OAuthTokenAccess tokenAccess;\n\n    private OAuthTokenResponse tokenResponse;\n    \n    @PostConstruct\n    public void initToken() {\n        tokenResponse = tokenAccess.getAccessToken(connection, request, request.getRequestURI());\n    }\n    \n    public MyView getResponse() {\n        if ( tokenResponse.hasValidToken() ) {\n            return doQuery(tokenResponse.getTokenValue());\n        }\n        \n        return null;\n    }\n    \n    public String getRedirectLink() {\n        if ( !tokenResponse.hasValidToken() ) {\n            return tokenResponse.getRedirectUri().toString();\n        }\n        \n        return null;\n    }\n}\n\n```\n\n### Servlets\n\nThe bundle exposes an abstract `OAuthEnabledSlingServlet` that contains the boilerplate code needed\nto obtain a valid OAuth 2 access token.\n\nBasic usage is as follows\n\n```java\n\nimport org.apache.sling.auth.oauth_client.*;\n\n@Component(service = { Servlet.class })\n@SlingServletPaths(value = \"/bin/myservlet\")\npublic class MySlingServlet extends OAuthEnabledSlingServlet {\n\n    private final MyRemoteService svc;\n   \n    @Activate\n    public MySlingServlet(@Reference OidcConnection connection, \n        @Reference OAuthTokenAccess tokenAccess,\n        @Reference MyRemoteService svc) {\n        super(connection, tokenAccess);\n        this.svc = svc;\n    }\n\n    @Override\n    protected void doGetWithToken(@NotNull SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response,\n            OAuthToken token) throws IOException, ServletException {\n\n        this.svc.query(\"my-query\", token.getValue()).writeResponseTo(response.getOutputStream());\n    }\n}\n```\n\n\n### Clearing access tokens\n\nIf an access token response contains an expiry date the bundle will make sure that it is not\naccessible via APIs. This will not cover all scenarios because access tokens can expire or be\ninvalidated out of band.\n\nThe client will need to determine if the access token is invalid as this is a provider-specific\ncheck.\n\n#### When the request and response are available\n\nThis method is generally recommended as it permits the generation of a redirect URI that will kick\noff a new OAuth authorisation flow.\n\n\n```java\n@Model(adaptables = SlingHttpServletRequest.class)\npublic class MySlingModel {\n    @OSGiService private OAuthTokenAccess tokenAccess;\n    @SlingObject SlingHttpServletRequest request;\n    @OSGiService(filter = \"(name=foo)\") private ClientConnection connection;\n    \n    public String getLink() {\n        // code elided\n        if ( accessTokenIsInvalid() ) {\n            OAuthTokenResponse response = tokenAccess.clearAccessToken(connection, request, request.getRequestURI());\n            return response.getRedirectUri().toString();\n        }\n    }\n}\n```\n\n\n#### When the request and response are not available\n\n\nThis approach should be used when invalidating access tokens without user interaction, as it does not\nprovide a mechanism to generate a redirect URL for restarting the OAuth authorisation flow and obtaining\na new access token.\n\n```java\n@Component\npublic class MyComponent {\n    @Reference private OAuthTokenAccess tokenAccess;\n    \n    public void execute(@Reference OidcConnection connection, ResourceResolver resolver) {\n        // code elided\n        if ( accessTokenIsInvalid() ) {\n            tokenAccess.clearAccessToken(connection, resolver);\n        }\n    }\n}\n```\n\n\n\n#### When extending OAuthEnabledSlingServlet\n\nFor classes that extend from the `OAuthEnabledSlingServlet` the `isInvalidAccessTokenException` method can be\noverriden. If this method returns true, the access token is cleared and a new OAuth flow is started.\n\n```java\n@Component(service = { Servlet.class })\n@SlingServletPaths(value = \"/bin/myservlet\")\npublic class MySlingServlet extends OAuthEnabledSlingServlet {\n\n    // other methods elided\n\n    @Override\n    protected boolean isInvalidAccessTokenException(Exception e) {\n        return e.getCause() instanceof InvalidAccessTokenException;\n    }\n}\n```\n\n\n### Error handling\n\nThe top-level servlets used for the OAuth flow will validate parameters that are expected to be\nsent by the client and return a status code of 400 in case the parameters are missing or invalid.\n\nFor others problems related to the OAuth flow these servlets throw specific subclasses of ServletException.\nThe exceptions will return generic messages that can be displayed directly to the user and store\nthe actual cause in nested exception so that it is logged.\n\nThese exceptions are:\n\n- `org.apache.sling.auth.oauth_client.impl.OAuthCallbackException`\n- `org.apache.sling.auth.oauth_client.impl.OAuthEntryPointException`\n- `org.apache.sling.auth.oauth_client.impl.OAuthFlowException` (superclass)\n\nIt is recommended that applications install specific error handlers for these exceptions. See the\n[Apache Sling error handling documentation](https://sling.apache.org/documentation/the-sling-engine/errorhandling.html)\nfor more details.\n\n### Client registration\n\nClient registration is specific to each provider. When registering, note the following:\n\n- 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\n- write down the client id, client secret obtained from the OIDC provider\n- you may need to provide in advance the set of scopes accessible to your client\n\nValidated providers:\n\n- Google, OIDC, with base URL of https://accounts.google.com , see [Google OIDC documentation](https://developers.google.com/identity/protocols/oauth2/openid-connect)\n- GitHub, OAuth 2.0, with authorizationEndpoint https://github.com/login/oauth/authorize and tokenEndpoint https://github.com/login/oauth/access_token\n- KeyCloak ( see [Keycloak](#keycloak) )\n- 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)\n- 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\n\n### Deployment\n\nA set of dependencies required by this bundle, on top of the Sling Starter ones, is available at `src/main/features/main.json`.\nFor 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`. \n\nSince the bundle relies on encryption to create and validate the OAuth 2.0 `state` parameter, a `CryptoService` must be configured\n\n```json\n    \"org.apache.sling.commons.crypto.internal.FilePasswordProvider~oauth\": {\n        \"path\": \"secrets/encrypt/password\",\n        \"fix.posixNewline\": true\n    },\n    \"org.apache.sling.commons.crypto.jasypt.internal.JasyptRandomIvGeneratorRegistrar~oauth\": {\n       \"algorithm\": \"SHA1PRNG\"\n    },\n    \"org.apache.sling.commons.crypto.jasypt.internal.JasyptStandardPbeStringCryptoService~oauth\": {\n       \"names\": [ \"sling-oauth\" ],\n       \"algorithm\": \"PBEWITHHMACSHA512ANDAES_256\"\n    }\n```\n\nThe _sling-oauth_ names property is important since it is used to select the CryptoService used by this bundle.\n\nIn addition, one of the following types of OSGi configuration must be added:\n\n#### OIDC variant\n\n```json\n\"org.apache.sling.auth.oauth_client.impl.OidcConnectionImpl~provider\": {\n    \"name\": \"provider\",\n    \"baseUrl\": \"https://example.com\",\n    \"clientId\": \"$[secret:provider/clientId]\",\n    \"clientSecret\": \"$[secret:provider/clientSecret]\",\n    \"scopes\": [\"openid\"]\n}\n```\n\n#### OAuth variant\n\n```json\n\"org.apache.sling.auth.oauth_client.impl.OAuthConnectionImpl~github\": {\n    \"name\": \"provider\",\n    \"authorizationEndpoint\": \"https://example.com/login/oauth/authorize\",\n    \"tokenEndpoint\": \"https://example.com/login/oauth/access_token\",\n    \"clientId\": \"$[secret:provider/clientId]\",\n    \"clientSecret\": \"$[secret:provider/clientSecret]\",\n    \"scopes\": [\"user:email\"]\n}\n```\n\nAt this point, the OAuth process can be kicked of by navigating to http://localhost:8080/system/sling/oauth/entry-point?c=provider\n\n### Token storage\n\nThe 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.\n\n#### JCR Storage\n\nThe tokens are stored under the user's home, under the `oauth-tokens/$PROVIDER_NAME` node.\n\n```json\n\"org.apache.sling.auth.oauth_client.impl.JcrUserHomeOAuthTokenStore\" : {\n}\n```\n\n#### Redis storage\n\n```json\n\"org.apache.sling.auth.oauth_client.impl.RedisOAuthTokenStore\" : {\n    \"redisUrl\": \"redis://localhost:6379\"\n}\n```\n\n## Local development setup\n\n### tl;dr\n\n- run the keycloak container using the instructions for 'use existing test files'\n- build the bundle once with `mvn clean install`\n- run Sling with `mvn feature-launcher:start feature-launcher:stop -Dfeature-launcher.waitForInput`\n- create OSGi config with \n\n```\nexport CLIENT_SECRET=$(cat src/test/resources/keycloak-import/sling.json | jq --raw-output '.clients[] | select (.clientId == \"oidc-test\") | .secret')\n\n$ curl -u admin:admin -X POST -d \"apply=true\" -d \"propertylist=name,baseUrl,clientId,clientSecret,scopes\" \\\n    -d \"name=keycloak-dev\" \\\n    -d \"baseUrl=http://localhost:8081/realms/sling\" \\\n    -d \"clientId=oidc-test\"\\\n    -d \"clientSecret=$CLIENT_SECRET\" \\\n    -d \"scopes=openid\" \\\n    -d \"factoryPid=org.apache.sling.auth.oauth_client.impl.OidcConnectionImpl\" \\\n    http://localhost:8080/system/console/configMgr/org.apache.sling.auth.oauth_client.impl.OidcConnectionImpl~keycloak-dev\n```\n\nNow you can \n\n- access KeyCloak on http://localhost:8081 \n- access Sling on http://localhost:8080\n- start the login process on http://localhost:8080/system/sling/oauth/entry-point?c=keycloak-dev\n\n### Keycloak\n\n#### Use existing test files\n\nNote that this imports the test setup with a single user with a _redirect_uri_ set to _http://localhost*_, which can be a security issue.\nIf 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\nand for the export directory.\n\n```\n$ mkdir -p keycloak-data/h2\n$ 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\n```\n\n#### Manual setup\n\n1. Launch Keycloak locally\n\n```\n$ 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\n```\n\n2. Create test realm\n\n- access http://localhost:8081/\n- go to 'Administration Console'\n- login with admin:admin\n- open dropdown from the top left and press 'Create realm'\n- Select the name 'sling' and create it\n\n3. Create client\n\n- in the left navigation area, press 'clients'\n- press 'Create client'\n- Fill in 'Client ID' as 'oidc-test' and press 'Next'\n- Enable 'Client authentication' and press 'Save'\n\n4. Configure clients\n\n- in the client details page, set the valid redirect URIs to http://localhost:8080/system/sling/oauth/callback and save\n- navigate to the 'Credentials' tab and copy the Client secret\n\n5. Add users\n\n- in the left navigation area, press 'users'\n- press 'create new user'\n- fill in username: test and press 'create'\n- go to the 'details' tab, clear any required user actions and press 'save'\n- go to the 'credentials' tab and press 'set password'\n- in the dialog, use 'test' for the password and password confirmation fields and then press 'save'\n- confirm by pressing 'save password' in the new dialog\n\n\n### Exporting the test realm\n\nCreate a directory to store the exported realm\n```\nmkdir -p $(pwd)/keycloak-data/export\n```\nExport the realm:\n```\n$ 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\n```\n\n### Future plans\n\n- explore an AuthenticationHandler that can optionally expose the access tokens\n- investigate PKCE (RFC 7636)\n- investigate encrypted client-side storage of tokens\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapache%2Fsling-org-apache-sling-auth-oauth-client","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fapache%2Fsling-org-apache-sling-auth-oauth-client","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapache%2Fsling-org-apache-sling-auth-oauth-client/lists"}