{"id":16537824,"url":"https://github.com/pablolec/sb_keycloak_saml","last_synced_at":"2025-07-25T10:35:48.508Z","repository":{"id":206661720,"uuid":"717362416","full_name":"PabloLec/sb_keycloak_saml","owner":"PabloLec","description":"SAML authentication implementation with Keycloak for IdP and SP integration.","archived":false,"fork":false,"pushed_at":"2024-11-29T19:09:49.000Z","size":261,"stargazers_count":12,"open_issues_count":5,"forks_count":2,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-04T15:53:50.417Z","etag":null,"topics":["authentication","iam","keycloak","keycloak-client","keycloak-provider","openid","openid-connect","saml","saml-idp","saml-service-provider","saml2","saml2-sp-sso"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"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/PabloLec.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"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}},"created_at":"2023-11-11T08:40:49.000Z","updated_at":"2025-03-07T10:19:08.000Z","dependencies_parsed_at":"2023-11-11T13:23:38.396Z","dependency_job_id":"6d462a79-d36f-4e22-bd8b-81d3c95d956d","html_url":"https://github.com/PabloLec/sb_keycloak_saml","commit_stats":null,"previous_names":["pablolec/sb_keycloak_saml"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/PabloLec/sb_keycloak_saml","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PabloLec%2Fsb_keycloak_saml","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PabloLec%2Fsb_keycloak_saml/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PabloLec%2Fsb_keycloak_saml/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PabloLec%2Fsb_keycloak_saml/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PabloLec","download_url":"https://codeload.github.com/PabloLec/sb_keycloak_saml/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PabloLec%2Fsb_keycloak_saml/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266991106,"owners_count":24017738,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-25T02:00:09.625Z","response_time":70,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["authentication","iam","keycloak","keycloak-client","keycloak-provider","openid","openid-connect","saml","saml-idp","saml-service-provider","saml2","saml2-sp-sso"],"created_at":"2024-10-11T18:43:38.308Z","updated_at":"2025-07-25T10:35:48.470Z","avatar_url":"https://github.com/PabloLec.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Goal\n\nThe primary objective of this project is to implement SAML authentication using Keycloak by setting up all the necessary components.\nOne instance of Keycloak serves as the Identity Provider (IdP), while another operates as the Service Provider (SP).\nAdditionally, a demo application is included to act as an application secured by the Keycloak SP, and the associated workflow exclusively accepts SAML authentications.\n\n```mermaid\nsequenceDiagram\n    actor User\n    participant Application as OAuth2\u003cbr\u003eApplication\n    participant SP as IAM /\u003cbr\u003eSAML Service Provider\n    participant IdP as SAML Identity Provider\n    User-\u003e\u003eApplication: Requests access\n    Application-\u003e\u003eSP: Checks if user is authenticated\n    SP-\u003e\u003eApplication: User not authenticated\n    Application-\u003e\u003eSP: Redirects user for authentication\n    SP-\u003e\u003eIdP: Redirects user for authentication\u003cbr\u003e(AuthnRequest)\n    IdP-\u003e\u003eUser: Prompts user to log in\n    User-\u003e\u003eIdP: Logs in\n    IdP-\u003e\u003eSP: Redirects authenticated user\u003cbr\u003e(SAML Response)\n    Note over SP: Maps user to existing account\u003cbr\u003eor dynamically creates a new user\n    SP-\u003e\u003eUser: Redirects authenticated user\u003cbr\u003e(OAuth2 Authorization Code)\n```\n\n## Context\n\n- Keycloak SP runs on `http://localhost:8081`\n- Keycloak IdP runs on `http://localhost:8082`\n- Demo app runs on `http://localhost:8083`\n- SP SAML broker descriptor can be obtained with `http://localhost:8081/realms/SP_realm/broker/IDP_SAML_SP_INITIATED/endpoint/descriptor`\n- IdP SAML descriptor can be obtained with `http://localhost:8082/realms/IdP_realm/protocol/saml/descriptor`\n\n## SP-initiated flow setup\n\n**On IdP:**\n- Create realm `IdP_realm`.\n- On realm `IdP_realm`, go to Realm Settings \u003e Keys \u003e Providers. Disable `rsa-generated` and click on Add Provider \u003e rsa and add a provider with private key (see `keys/idp_private_key.pem`).\n\n**On SP:**\n- Create realm `internal`.\n- Create client `app` on realm `internal` for an example Python app with `Client authentication` on (for client ID / client secret).\n- Create realm `SP_realm`.\n- On realm `SP_realm`, go to Realm Settings \u003e Keys \u003e Providers. Disable `rsa-generated` (or lower its priority) and click on Add Provider \u003e rsa then add a provider with private key (see `keys/sp_private_key.pem`).\n- Create an OpenID Connect client on realm `SP_realm` with client id `OIDC_FRONTEND_CLIENT` setting `Root URL`, `Home URL` and `Valid redirect URIs` to `http://localhost:8083/`.\n- Add SAML identity provider with alias `IDP_SAML_SP_INITIATED` on `SP_realm` with `Service provider entity ID` set to `SP_SAML_SP_INITIATED` and `SAML entity descriptor` set to `http://localhost:8082/realms/IdP_realm/protocol/saml/descriptor`.\n- Create a new authentication flow with name `SAML_IDP_FLOW` in Authentication \u003e Create flow. Add an execution, choose `Identity Provider Redirector`, set it as required and click on the cog icon to edit its config and set `IDP_SAML_SP_INITIATED` as default identity provider.\n- Go back to client `OIDC_FRONTEND_CLIENT`, got to Advanced section and set `SAML_IDP_FLOW` as browser flow in Authentication flow overrides.\n- To dynamically create users on the SP without prompting the user to fill a form, go to `Authentication` create a new flow `CUSTOM_FIRST_BROKER_LOGIN_FLOW`, add two steps `Create User If Unique` and `Automatically set existing user` and set both as `Alternative`. Now go back to your newly added Identity Provider and set `CUSTOM_FIRST_BROKER_LOGIN_FLOW` as `First login flow override`.\n- Go to Realm roles and create a role named `CUSTOMER`.\n- Go to Identity Providers \u003e `IDP_SAML_SP_INITIATED` \u003e Mappers and create a new mapper with type `Hardcoded Role` and value `CUSTOMER`.\n\n**Back to IdP:**\n- Create a new client on realm `IdP_realm` with the UI using Clients \u003e Import Client and import SP SAML XML descriptor.\n\n## IdP-initiated flow\n\nThe sequence set above refers to the SP-Initiated flow, where the user first accesses our application and is then redirected to the IdP.    \nIt's also possible to implement an IdP-Initiated flow, where the user first accesses the IdP and is then redirected to the application. This may be necessary if the IdP does not properly implement the SAML standard. For example, Google SAML does not retain the RelayState parameter value. However, RelayState is crucial for Keycloak during an SP-Initiated flow as it stores a unique session ID there.    \nSetting up a SAML client for this flow is relatively simple, though it's less intuitive if there's a need to redirect to an OAuth2 client to achieve behavior similar to the SP-Initiated flow described above.  \nThis functionality is undocumented, and there might be a \"cleaner\" way to do it. My solution here is to redirect the user to the Oauth2 client login page after SAML authentication. The goal is to use the session cookie to implicitly connect the user on the OAuth2 client and redirect them to the application.\n\nYou will find in the project a functional example of the IdP-Initiated flow resulting in a redirection to the application with an Authorization Code. Here are some important configuration elements:  \n\n**On the SP side:**\n- I had to create a second IdP with a different `Service provider entity ID` (here `SP_SAML_IDP_INITIATED`) because Keycloak, as an IdP, does not allow creating two SAML clients with the same entity ID. This is solely a constraint due to the demo requirements. The IdP is completely identical to the first one.\n- I also had to duplicate the OAuth2 client for demonstration purposes. You'll find the client `OIDC_FRONTEND_CLIENT_IDP_INITIATED`, which is identical to `OIDC_FRONTEND_CLIENT`, the only difference being that the `Browser Flow` has no override. Here we do not want to force the user to go through the IdP since they are already coming from it.\n- Create a SAML client `SAML_CLIENT_IDP_INITIATED`, set `idp-initiated` as the value for `IDP-Initiated SSO URL name` so that KC declares an endpoint `http://localhost:8081/realms/SP_realm/broker/IDP_SAML_IDP_INITIATED/endpoint/clients/idp-initiated` which will be able to process the SAML assertions from the IdP.\n- On this client, disable `Force POST binding` and, in `Advanced`, set the `Assertion Consumer Service Redirect Binding URL` to `http://localhost:8081/realms/SP_realm/protocol/openid-connect/auth?client_id=OIDC_FRONTEND_CLIENT_IDP_INITIATED\u0026response_type=code`. The goal is to redirect (rather than POST, which will not be supported) to the OAuth2 login page after the user session has been opened via SAML auth.\n\n**On the IdP side:**\n- Create a new SAML client with the correct Client ID/Entity ID (here `SP_SAML_IDP_INITIATED`) and again `idp-initiated` as the value for `IDP-Initiated SSO URL name`.\n- Here we can simply set `http://localhost:8081/realms/SP_realm/broker/IDP_SAML_IDP_INITIATED/endpoint/clients/idp-initiated` as the `Master SAML Processing URL`, and POST binding will be supported.\n\nFor testing, refer to the [Testing](#testing) section.\n\n\n## Startup\n\nDocker compose runs 5 containers: Keycloak IdP, Keycloak SP, a demo Python app and two Postgres instances for KC.  \nKeycloak containers load pre-configured realms stored in the `realms` folder.  \nOn startup, the Python app waits for Keycloak to be ready then creates a new user on IdP with username `john` and password `john`.\n\n## Notes\n\n#### Private keys\nYou can of course keep RSA auto-generated keys, but custom ones were added on both sides to demonstrate how to use them.  \nRSA private keys are included in the exported realms JSON in plain text for convenience.  \nPrivate keys should be kept secret and not be shared in a real environment. As of version 25.0, Keycloak stores private keys in the database as entries in the `component_config` table.    \nFor reference:  \n```sql\nSELECT * FROM component C\nJOIN component_config CC ON C.id = CC.component_id\nWHERE C.name = 'rsa' -- Name manually set when adding key provider on the UI\nAND CC.name = 'privateKey'\n```\n\nFor managing secrets and Keycloak configuration in general, I recommend using https://github.com/adorsys/keycloak-config-cli.\n\n#### Authentication Flow\nA custom authentication flow is used to ensure that user connections go through the IdP as the only option, without any prompt.  \nAlternatively, this can be achieved by using the `kc_idp_hint` query parameter in the first auth redirect request made by the frontend client.  \n\n#### User Creation\nUsers are dynamically created on the SP without prompting the user to fill out a form. The email provided by the IdP is used as the username, so when the user logs in again, the same account is used.  \nThe username format is determined by the NameIDFormat displayed in both SAML entities descriptors. If multiple NameIDFormat options are available, as in this case, the IdP selects its preferred option, and Keycloak implicitly chooses the persistent format. This can be overridden in the client config on the IdP.  \nIf needed, additional information can be added to the IdP response and mapped to user attributes on the SP, such as email, first name, etc.  \nRoles are provided by the IdP, but for demo purposes, a role (`CUSTOMER`) is hardcoded on the SP.  \n\n## How to run\n\n- `docker-compose up`\n- Follow instructions in [Testing](#testing)\n\n## Testing\n\n### SP-initiated\n\n1. Go to http://localhost:8083 (Demo Python app that acts as the KC protected service/frontend)\n2. You should be redirected to the IdP login page\n3. Login with username `john` and password `john`\n4. You should be redirected to the SP which will create a new user account\n5. You should be redirected to the demo app and see a welcome message\n\n\n### IdP-initiated\n\n1. Go to http://localhost:8082/realms/IdP_realm/protocol/saml/clients/idp-initiated (IdP Initiated SSO URL)\n2. Login with username `john` and password `john`\n3. You should be redirected to the SP which will create a new user account\n4. You should be redirected to the demo app and see a welcome message","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpablolec%2Fsb_keycloak_saml","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpablolec%2Fsb_keycloak_saml","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpablolec%2Fsb_keycloak_saml/lists"}