In this tutorial I will show you how to use OIDC on top of OAuth2 to implement authentication and authorization.
If you are interested in simple OAuth2 authorization see Securing migrator with OAuth2.
OpenID Connect (OIDC) is an authentication layer on top of OAuth2 authorization framework.
I will use oauth2-proxy project. It supports multiple OAuth2 providers. To name a few: Google, Facebook, GitHub, LinkedIn, Azure, Keycloak, login.gov, or any OpenID Connect compatible provider.
As an OIDC provider I will re-use oauth2-proxy local-environment which creates and setups a ready-to-use Keycloak server. I extended the Keycloak server with additional configuration (client mappers to include roles in responses), created two migrator roles and two additional test accounts.
I also put haproxy between oauth2-proxy and migrator. haproxy will validate the JWT access token and implement access control based on user's roles to allow or deny access to underlying migrator resources. I re-used a great lua script written by haproxytech folks which I modified to work with Keycloak realm roles.
To learn more about oauth2-proxy visit https://github.com/oauth2-proxy/oauth2-proxy.
To learn more about Keycloak visit https://www.keycloak.org.
To learn more about haproxy jwtverify lua script visit https://github.com/haproxytech/haproxy-lua-jwt.
The provided docker-compose.yaml
provision the following services:
- keycloak - the Identity and Access Management service, available at: http://keycloak.localtest.me:9080
- oauth2-proxy - proxy that protects migrator and connects to keycloak for OAuth2/OIDC authentication, available at: http://gateway.localtest.me:4180
- haproxy - proxy that contains JWT access token validation and user access control logic, available at: http://haproxy.localtest.me:8080
- migrator - deployed internally and accessible only from haproxy and only by authorized users
Note: above setup doesn't have a database as this is to only illustrate how to setup OIDC
To build the test environment execute:
docker-compose up -d
I created 2 test users in Keycloak:
[email protected]
- migrator admin, has the following roles:migrator_admin
andmigrator_user
[email protected]
- migrator user, has one migrator role:migrator_user
In haproxy.cfg I implemented the following sample rules:
- all requests starting
/v1
will return 403 Forbidden - to access
/v2/service
user must havemigrator_user
role - to access
/v2/config
user must havemigrator_admin
role
There are two ways to access migrator:
- getting JWT access token via oauth2-proxy - shown in orange
- getting JWT access token directly from Keycloak - shown in blue
Let's test them.
oauth2-proxy - [email protected]
- Access http://gateway.localtest.me:4180/
- Authenticate using username:
[email protected]
and password:password
. - After a successful login you will see
/
response - Open http://gateway.localtest.me:4180/v2/config and you will see 403 Forbidden - this user doesn't have
migrator_admin
role - Logout from Keycloak http://keycloak.localtest.me:9080/auth/realms/master/protocol/openid-connect/logout
- Invalidate session on auth2-proxy (oauth2-proxy cookie expires in 15 minutes) http://gateway.localtest.me:4180/oauth2/sign_out
oauth2-proxy - [email protected]
- Access http://gateway.localtest.me:4180/
- Authenticate using username:
[email protected]
and password:password
. - After a successful login you will see successful
/
response - Open http://gateway.localtest.me:4180/v2/config and now you will see migrator config
- Logout from Keycloak http://keycloak.localtest.me:9080/auth/realms/master/protocol/openid-connect/logout
- Invalidate session on auth2-proxy (oauth2-proxy cookie expires in 15 minutes) http://gateway.localtest.me:4180/oauth2/sign_out
- Get JWT access token for the
[email protected]
user:
access_token=$(curl -s http://keycloak.localtest.me:9080/auth/realms/master/protocol/openid-connect/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d '[email protected]' \
-d 'password=password' \
-d 'grant_type=password' \
-d 'client_id=oauth2-proxy' \
-d 'client_secret=72341b6d-7065-4518-a0e4-50ee15025608' | jq -r '.access_token')
- Execute migrator action and pass the JWT access token in HTTP Authorization header:
curl http://haproxy.localtest.me:8080/v2/config \
-H "Authorization: Bearer $access_token"
You can copy JWT access token (haproxy log or Keycloak REST API) and decode it on https://jwt.io.
You can verify the signature of the JWT token by providing the public key (keycloak.pem
available in haproxy folder).
Public key can be also fetched from:
curl http://keycloak.localtest.me:9080/auth/realms/master/
The response is a JSON and the public key is returned as a string. To be a valid PEM format you need to add
-----BEGIN PUBLIC KEY-----
header,-----END PUBLIC KEY-----
footer, and break that string into lines of 64 characters. Comparekeycloak.pem
with the above response.