What is OpenID Connect?
- The OpenID Connect (hereafter, OIDC) provides an identity layer on top of OAuth 2.0
- Designed for user authentication
Tech Stack
The tech stack used for this article includes:
- RHEL
- Ubuntu
- Keycloak
- Docker
- Flask
- HTML/CSS and JS
Sequence diagram
TODO
Keycloak
Add the user in docker group:
sudo usermod -aG docker ${USER}
Start Keycloak
docker run -p 8080:8080 \
-e KC_BOOTSTRAP_ADMIN_USERNAME=admin \
-e KC_BOOTSTRAP_ADMIN_PASSWORD=admin \
quay.io/keycloak/keycloak:26.1.0
Log in to the Admin Console: localhost:8080
Create realm
A realm manages a set of users, credentials, roles, and groups.
Create user
Set password for this user:
WordPress
Create a client
In Keycloak, clients are applications or services.
Click Next
Turn on Client authentication
On
means confidential access typeOff
means public access type
Select Standard Flow
as Authentication flow. This enables support of Authorization Code Flow
for this client.
Click Next
Goto to the Credentials
tab, and copy Client Secret
for later use.
That's all for Keyclock client setup. Now, let's setup a WordPress website.
Docker compose
Create docker-compose.yaml
inside wordpress
folder:
services:
wordpress:
image: wordpress:latest
container_name: wpTutorial
restart: always
ports:
- "5001:80"
environment:
WORDPRESS_DB_HOST: dbWP:3306
WORDPRESS_DB_NAME: wpTutorial
WORDPRESS_DB_USER: root
WORDPRESS_DB_PASSWORD: s3crets
depends_on:
- db
db:
image: mysql:5.7
container_name: dbWP
restart: always
environment:
MYSQL_ROOT_PASSWORD: s3crets
MYSQL_DATABASE: wpTutorial
volumes:
- db_data:/var/lib/mysql
volumes:
db_data:
driver: local
- This Docker compose file defines two servcies i.e.,
WordPress
andMySQL
- The WordPress website can be browsed from
http://192.168.0.123:5001
(replace with your IP)
Now, run docker compose:
docker compose up -d
WordPress setup
Browse http://192.168.0.123:5001, and run the WordPress setup.
Login as admin
on http://192.168.0.123:5001/wp-admin/
Note: The objective of this section is to enable Keycloak's OpenID Connect functionality for users within Keycloak.
WordPress OpenID Connect plugin
Install and active the plugin called OpenID Connect Generic Client
Go to http://KEYCLOAK_IP:PORT/realms/YOUR_REALM/.well-known/openid-configuration
Or, go to Keycloak admin page > Real settings
> and click on OpenID Endpoint Configuration
as shown below:
As this will be important to fill out the OpenID Connect Client
settings.
Goto Settings
> OpenID Connect Client
settings, and fill out inputs as mentioned below:
Key | Value |
---|---|
Login Type | OpenID Connect button on login form |
Client ID | wordpress-client |
Client Secret Key | qFk2y6wqro68QukoKaehGXoEn9bXVJB0 |
OpenID Scope | email profile openid offline_access |
Login Endpoint URL | http://192.168.0.123:8080/realms/Tutorial/protocol/openid-connect/auth |
Userinfo Endpoint URL | http://192.168.0.123:8080/realms/Tutorial/protocol/openid-connect/userinfo |
Token Validation Endpoint URL | http://192.168.0.123:8080/realms/Tutorial/protocol/openid-connect/token |
End Session Endpoint URL | http://192.168.0.123:8080/realms/Tutorial/protocol/openid-connect/logout |
ACR values | leave this empty |
Identity Key | preferred_username |
Disable SSL Verify | leave this empty |
HTTP Request Timeout | 5 |
Nickname Key | preferred_username |
Email Formatting | {email} |
Display Name Formatting | leave this empty |
Identify with User Name | leave this empty |
State time limit | 180 |
Enable Refresh Token | enabled |
Link Existing Users | leave this empty |
Create user if does not exist | enabled |
Redirect Back to Origin Page | leave this empty |
Redirect to the login screen when session is expired | enabled |
Enforce Privacy | leave this empty |
Alternate Redirect URI | leave this empty |
Enable Logging | leave this empty |
Log Limit | 1000 |
And save changes.
Login with OpenID Connect
Now, browse http://192.168.0.123:5001/wp-admin
Click on Login with OpenID Connect
Sign with Keycloak user testuser
and password s3crets
Flask
Create a client
Create a Keycloak client flask-client
as mentioned below:
- Client ID: flask-client
- Name: Flask project client
- Client authentication: On
- Authorization: Off
- Authentication flow
- Standard flow: Enabled
- Direct access grants: Disabled
- Root URL:
http://192.168.0.123:5002
- Home URL: not required
- Valid redirect URIs:
http://192.168.0.123:5002/*
- Valid post logout redirect URIs: not required
- Web origins: *
Click Save
Now, make a note of client secrets for later use.
Flask
Create flask-project
mkdir flask-project
cd flask-project
docker-compose.yaml
Create docker-compose.yaml
touch docker-compose.yaml
Paste the following yaml
:
services:
web:
build: .
ports:
- "5002:5000"
develop:
watch:
- action: sync
path: .
target: /code
Dockerfile
Create Dockerfile
touch Dockerfile
Paste:
WORKDIR /code
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
EXPOSE 5000
COPY . .
CMD ["flask", "run", "--debug"]
requirements.txt
Create requirements.txt
flask
requests
index.html
Create index.html
mkdir templates
touch index.html
<html>
<body>
<p>
Welcome, {{ user_info_obj.preferred_username }}
</p>
<hr>
<p>{{ user_info_obj.email }}</p>
<p>{{ user_info_obj.email_verified }}</p>
<p>{{ user_info_obj.upn }}</p>
<p>
<a href="/logout">Logout</a>
</p>
</body>
</html>
app.py
Create app.py inside flask-project
pwd
/home/tux/opt/tutorial/flask-project
touch app.py
Paste the following code:
import os
import requests
from flask import Flask, redirect, request, session, url_for, render_template
app = Flask(__name__)
app.secret_key = os.urandom(24)
# Keycloak settings
KEYCLOAK_SERVER = 'http://192.168.0.123:8080'
REALM_NAME = 'Tutorial'
CLIENT_ID = 'flask-client'
CLIENT_SECRET = 'yakhrUZBx8aIzDPH2FqiRBE286EqYEzm'
REDIRECT_URI = 'http://192.168.0.123:5002/callback'
# Keycloak OIDC endpoints
AUTHORIZATION_URL = f"{KEYCLOAK_SERVER}/realms/{REALM_NAME}/protocol/openid-connect/auth"
TOKEN_URL = f"{KEYCLOAK_SERVER}/realms/{REALM_NAME}/protocol/openid-connect/token"
USERINFO_URL = f"{KEYCLOAK_SERVER}/realms/{REALM_NAME}/protocol/openid-connect/userinfo"
@app.route('/')
def home():
'''
'''
if 'user_info' in session:
user_info_obj = session['user_info']
return render_template('index.html', user_info_obj=user_info_obj)
return redirect(url_for('login'))
@app.route('/login')
def login():
'''
Redirect to Keycloak login page
'''
auth_url = f"{AUTHORIZATION_URL}?client_id={CLIENT_ID}&response_type=code&scope=openid&redirect_uri={REDIRECT_URI}"
return redirect(auth_url)
@app.route('/callback')
def callback():
'''
'''
# Get authorization code from Keycloak
code = request.args.get('code')
# Request access token using the code
token_data = {
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': REDIRECT_URI,
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET
}
response = requests.post(TOKEN_URL, data=token_data)
response_data = response.json()
if 'access_token' not in response_data:
return "Error: No access token found.", 400
access_token = response_data['access_token']
# Fetch user info from Keycloak
user_info_response = requests.get(
USERINFO_URL,
headers={'Authorization': f'Bearer {access_token}'}
)
if user_info_response.status_code != 200:
return "Error: Could not retrieve user info.", 400
user_info = user_info_response.json()
# Store user info in session
session['user_info'] = user_info
return redirect(url_for('home'))
@app.route('/logout')
def logout():
session.pop('user_info', None)
logout_url = f"{KEYCLOAK_SERVER}/realms/{REALM_NAME}/protocol/openid-connect/logout"
return redirect(logout_url)
The flask-project
structure should look like this:
flask-project/
├── Dockerfile
├── app.py
├── docker-compose.yaml
├── requirements.txt
└── templates
└── index.html
Now, let's run the docker
docker compose up
Output:
Browse http://192.168.0.123:5002/
As soon as you enter http://192.168.0.123:5002/
, the browser address gets redirect to http://192.168.0.123:8080/realms/Tutorial/protocol/openid-connect/auth?client_id=flask-client&response_type=code&scope=openid&redirect_uri=http://192.168.0.123:5002/callback
with Keycloak login page.
Let's decode this:
URL / URL fragments | Description |
---|---|
http://192.168.0.123:8080/realms/Tutorial/protocol/openid-connect/auth |
AUTHORIZATION_URL |
?client_id=flask-client |
Client that we've created on the Keycloak |
&response_type=code |
Response type is code |
&scope=openid |
Scope is openid |
&redirect_uri=http://192.168.0.123:5002/callback |
This is redirect URI |
Now, Login with Keycloak user's credentials
With the correct credentials, you'll be granted access to the flask-client
application
And logout shall end the session