The primary objective of this project is to implement SAML authentication using Keycloak by setting up all the necessary components. One instance of Keycloak serves as the Identity Provider (IdP), while another operates as the Service Provider (SP). Additionally, a demo application is included to act as an application secured by the Keycloak SP, and the associated workflow exclusively accepts SAML authentications.
sequenceDiagram
actor User
participant Application
participant SP as Service Provider
participant IdP as Identity Provider
User->>Application: Requests access
Application->>SP: Checks if user is authenticated
SP->>Application: User not authenticated
Application->>SP: Redirects user for authentication
SP->>IdP: Redirects user for authentication
IdP->>User: Prompts user to log in
User->>IdP: Logs in
IdP->>SP: Redirects authenticated user
Note over SP: Maps user to existing account or dynamically creates a new user
SP->>Application: Redirects authenticated user
- Keycloak IdP runs on
http://localhost:8081
- Keycloak SP runs on
http://localhost:8082
- Demo app runs on
http://localhost:8083
- IdP SAML descriptor can be obtained with
http://localhost:8082/realms/IdP_realm/protocol/saml/descriptor
- SP SAML broker descriptor can then be obtained with
http://localhost:8081/realms/SP_realm/broker/saml/endpoint/descriptor
On IdP:
- Create realm
IdP_realm
. - On realm
IdP_realm
, go to Realm Settings > Keys > Providers. Disablersa-generated
and click on Add Provider > rsa and add a provider with private key (seekeys/idp_private_key.pem
).
On SP:
- Create realm
internal
. - Create client
app
on realminternal
for an example Python app withClient authentication
on (for client ID / client secret). - Create realm
SP_realm
. - On realm
SP_realm
, go to Realm Settings > Keys > Providers. Disablersa-generated
(or lower its priority) and click on Add Provider > rsa then add a provider with private key (seekeys/sp_private_key.pem
). - Create an OpenID Connect client on realm
SP_realm
with client idfrontend
setting "Root URL", "Home URL" and "Valid redirect URIs" tohttp://localhost:8083/
. - Add SAML identity provider with name
saml
onSP_realm
with "Service provider entity ID" set tohttp://localhost:8081/realms/SP_realm
and "SAML entity descriptor" set tohttp://localhost:8082/realms/IdP_realm/protocol/saml/descriptor
. - Set
saml
as default identity provider (to bypass default login form) onSP_realm
by going to Authentication > Flows >browser
, then clicking on the cog icon and setting "Default identity provider" tosaml
(with any alias). - To dynamically create users on the SP without prompting the user to fill a form, go to Authentication >
first broker login
> and disableReview Profile
. - Go to Realm roles and create a role named
CUSTOMER
. - Go to Identity Providers >
saml
> Mappers and create a new mapper with typeHardcoded Role
and valueCUSTOMER
.
Back to IdP:
- Create a new client on realm
IdP_realm
with the UI using Clients > Import Client and import SP SAML XML descriptor.
Docker compose runs 5 containers: Keycloak IdP, Keycloak SP, a demo Python app and two Postgres instances for KC.
Keycloak containers load pre-configured realms stored in the realms
folder.
On startup, the Python app waits for Keycloak to be ready then creates a new user on IdP with username john
and password john
.
RSA private keys were added in exported realms JSON in plain text for convenience.
Private keys should be kept secret and not shared in a real environment. As of version 23.0, Keycloak stores private keys in database as an entry in the component_config
table.
For reference:
SELECT * FROM component C
JOIN component_config CC ON C.id = CC.component_id
WHERE C.name = 'rsa' -- Name manually set when adding key provider on the UI
AND CC.name = 'privateKey'
User is redirected automatically to IdP for authentication using kc_idp_hint=saml
query parameter.
Otherwise, this could be achieved by setting SAML IdP as the only required step in the browser authentication flow. (Authentication > Flows > browser
)
User is dynamically created on the SP without prompting the user to fill a form. The persistent ID provided by IdP is used as the username and thus next time the user logs in, the same account is used.
If needed, more information could be added to the IdP response and mapped to the user attributes on the SP such as email, first name, etc.
Roles are already provided by the IdP but for the demo purpose, a role (CUSTOMER
) is hardcoded on the SP.
docker-compose up
- Follow instructions in Testing
- Go to
http://localhost:8083
(Demo Python app that acts as the KC protected service/frontend). - You should be redirected to the IdP login page.
- Login with username
john
and passwordjohn
. - You should be redirected to the SP which will create a new user account.
- Finally, you should be redirected to the demo app and see a welcome message.