-
Notifications
You must be signed in to change notification settings - Fork 0
Single Sign On
- Trick :just do a CTRL+click (on Windows and Linux) or CMD+click (on MacOS) on to open links in a new tab
- Implement Single Sign-On (SSO) authentication for a web application without modifying it.
-
We use OpenID Connect (OIDC) authentication mechanism which is a thin layer that sits on top of OAuth 2.0, with Keycloak as the identity provider (IdP), and OpenResty as the relying party (RP)
-
The OpenID standard defines a situation whereby a cooperating site can act as an RP, allowing the user to log into multiple sites using one set of credentials. The user benefits from not having to share their login credentials with multiple sites, and the operators of the cooperating site avoid having to develop their own login mechanism.
-
In an Nginx and Keycloak architecture:
- See source code : https://github.com/djacob65/maggot-sso
- keycloak: we need to create a realm(*) (here Maggot):
(*) A realm is a space where you manage objects, including users, applications, roles, and groups. A user belongs to and logs into a realm. One Keycloak deployment can define, store, and manage as many realms as there is space for in the database
- keycloak: Then at least one user(*) has to be created:
(*) Keycloak users are stored locally and not connected to an external identity source. Local Keycloak users can be used to create and manage service accounts that are not associated with individual end users.
- For an online help : Keycloak – Create Realm, Client, Roles, and User
- We need to create first client(*), to authenticate users who want to access the web application, involving a redirect to the identity provider
(*) Clients are entities that can request Keycloak to authenticate a user. Most often, clients are applications and services that want to use Keycloak to secure themselves and provide a single sign-on solution. Clients can also be entities that just want to request identity information or an access token so that they can securely invoke other services on the network that are secured by Keycloak.
- We have the following sequence diagram of the standard flow:
- keycloak: Creating and configuring a client : only the standard flow needs to be specified to make redirections
- keycloak: We then create some roles(*) directly attached to the client
(*) These roles are specific to individual client applications within the realm. Each client can have its own set of roles that define access permissions specific to that application.
- keycloak: We then assign one of the roles to the previously created user.
- nginx: The corresponding configuration part in the nginx.conf file
require("sessions_store")
local opts = {
redirect_uri_path = "/maggot/Maggot/redirect_uri",
discovery = "http://10.0.0.106:8080/realms/Maggot/.well-known/openid-configuration",
client_id = "maggot",
client_secret = "GUWHrrBXnJp3dtT3Nl15olqDgyxaGGx2",
scope = "openid email",
access_token_expires_leeway = 30,
renew_access_token_on_expiry = true,
redirect_uri_scheme = "http",
logout_path = "/maggot/logout",
revoke_tokens_on_logout = true,
redirect_after_logout_uri = "http://10.0.0.106:8080/realms/Maggot/protocol/openid-connect/logout?client_id=maggot",
redirect_after_logout_with_id_token_hint = false,
session_contents = {id_token=true, access_token=true},
session_store = shared_dict_session_store("session_cache_realm1")
}
local res, err = require("resty.openidc").authenticate(opts,nil,nil,{name=opts.client_id, audience=opts.client_id})
if err then
ngx.status = 403
ngx.exit(ngx.HTTP_FORBIDDEN)
end
ngx.req.set_header("Authorization", "Bearer " .. res.access_token)
- User's web browser: The sequence diagram of the standard flow can be traced with the development module of your browser:
- Note: It is important to highlight that a session is managed per realm (here session_cache_realm1 for Maggot realm). This allows the realms to be isolated from each other. We can thus 1) either create another client dedicated to an application in the same realm, and in this case the connection is valid for all applications in the realm (role of SSO), 2) or create another realm with its own session. Connections for one realm do not interfere with another realm, as long as each realm has its own session.
- User's web browser: When you want to access the application's web page (here http://10.0.0.106/maggot/), you are redirected to the keycloak login page (assuming you have chosen the maggot theme in the realm configuration regarding the login function (see How to customize the login page)
- See an online example
- After authentication, we can retrieve the token within the Maggot application (or another one) to decode it to retrieve its payload.
- See information related to certain programming languages : PHP, Python, R, Javascript or to certain frameworks: PHP Symfony, Django.
- User's web browser: To log out, simply call the url http://10.0.0.106/maggot/logout and you will get the keycloak logout page.
- We need to create a second client to directly access the resources provided by the web API using a JSON Web Token (JWT). We have the following sequence diagram of the direct access flow:
- keycloak: Creating and configuring a client : the both 'direct access grants' and 'service accounts roles' are needed.
- Note: In the same way as for the previous client, we create roles directly attached to the client and then assign one of the roles to the user.
- nginx: The corresponding configuration part in the nginx.conf file
local opts = {
discovery = "http://10.0.0.106:8080/realms/Maggot/.well-known/openid-configuration",
client_id = "api-maggot",
client_secret = "FYFBOxpWl6spQ9of62ljGhR7v6NcnBS7",
session_contents = {id_token=true}
}
-- call bearer_jwt_verify for OAuth 2.0 JWT validation
local res, err = require("resty.openidc").bearer_jwt_verify(opts)
if err then
ngx.status = 403
ngx.exit(ngx.HTTP_FORBIDDEN)
end
# Keycloak variables
CLIENT_ID='api-maggot'
CLIENT_SECRET='FYFBOxpWl6spQ9of62ljGhR7v6NcnBS7'
TOKEN_URL='http://10.0.0.106:8080/realms/Maggot/protocol/openid-connect/token'
# User credentials
USERNAME='djacob'
PASSWORD='djpassword'
# Web API URL
API_URL='http://10.0.0.106/maggot/metadata/frim1?format=maggot'
# Get Access Token
JSON=$(curl -s -H 'Content-Type: application/x-www-form-urlencoded' \
-d "client_id=$CLIENT_ID" -d "client_secret=$CLIENT_SECRET" \
-d 'grant_type=password' -d "username=$USERNAME" -d "password=$PASSWORD" $TOKEN_URL)
TOKEN=$(echo $JSON | jq -r '.access_token')
REFRESH_TOKEN=$(echo $JSON | jq -r '.refresh_token')
# Extracting the payload from the access token (optional)
echo $TOKEN | sed -e "s/\./\n/g" | head -n -1 | tail -1 | base64 --decode 2>/dev/null | jq
# Make a request to the protected API
# Note: API-KEY is needed in the header
curl -s -H 'accept: application/json' -H "API-KEY: XX" -H "Authorization: Bearer $TOKEN" \
-X GET $API_URL; echo; echo;
- Note: it is necessary to specify in the header of the request the 'API-KEY' variable with any value. Indeed it is by this means that we can differentiate between the two types of access (direct access vs redirection to the login page). See nginx.conf:
if ngx.req.get_headers()["API-KEY"] ~= nil then
... direct access ...
else
... redirection to the login page ...
end
- Example of a payload of a JWT:
{
"exp": 1729776742,
"iat": 1729776442,
"jti": "0d08be5c-e0b0-4b89-8680-848589d0eb9c",
"iss": "http://10.0.0.106:8080/realms/Maggot",
"aud": "account",
"sub": "233a2dc3-b7df-46b2-9d5e-52bd17fd00b6",
"typ": "Bearer",
"azp": "api-maggot",
"sid": "78b62332-463f-4271-a40f-b38e91546260",
"acr": "1",
"allowed-origins": [
"*"
],
"realm_access": {
"roles": [
"offline_access",
"uma_authorization",
"default-roles-maggot"
]
},
"resource_access": {
"api-maggot": {
"roles": [
"team1"
]
},
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
},
"maggot": {
"roles": [
"team1"
]
}
},
"scope": "profile email",
"email_verified": false,
"name": "Daniel Jacob",
"preferred_username": "djacob",
"given_name": "Daniel",
"family_name": "Jacob",
"email": "[email protected]"
}
- How to Connect to an External Identity Provider Using OIDC - Ref Doc
- How to Connect to an External Identity Provider Using SAML - Ref Doc
- Ariane Service: Single and secure authentication for web applications