Many systems need to have Authentication and Authorization to manage users and accesses. The completed way to handle it is OpenID Connect or OIDC, which is on top of OAuth2.
Architecture
What we want to achieve in the end is this architecture:
Terms
Before jumping into the details, let’s review some terms and definitions:
All these definitions are according to Wikipedia.
- Authentication: is the act of proving an assertion, such as the identity of a computer system user.
- Authorization: Authorization is the function of specifying access rights/privileges to resources, which is related to general information security and computer security, and to access control in particular.
- OAuth: s an open standard for access delegation, commonly used as a way for internet users to grant websites or applications access to their information on other websites but without giving them the passwords.
- OpenID Connect: is an open standard and decentralized authentication protocol promoted by the non-profit OpenID Foundation.
- Access Token: contains the security credentials for a login session and identifies the user, the user’s groups, the user’s privileges, and, in some cases, a particular application.
- JWT: is a proposed Internet standard for creating data with optional signature and/or optional encryption whose payload holds JSON that asserts some number of claims.
- Claims-based Identity: is a common way for applications to acquire the identity information they need about users inside their organization, in other organizations, and on the Internet.
Keycloak
We have several options to set up OIDC, but in this post, I’m going to use Keycloak service. Keycloak supports OIDC and SAML. Based on the Keycloak official website, it is an “Open Source Identity and Access Management” service.
I will install and run it by Docker.
To have a connection between our Docker containers, we need to have a network:
$ docker network create keycloak-tyk
Now it’s time to run the Keycloak container:
$ docker run -d \
--net keycloak-tyk \
-p 8080:8080 \
--name keycloak \
-e KEYCLOAK_HOSTNAME=keycloak -e KEYCLOAK_ADMIN='admin' -e KEYCLOAK_ADMIN_PASSWORD='hxA3ibCP' \
quay.io/keycloak/keycloak:18.0.0 start-dev
As you may already notice, it’s a development setup, not production. So please be careful with the production setup.
We’re exposing the port 8080
, and the username and password of the Keycloak admin panel are there. For sure, you have to change the password to something else.
In order to generate a good password, you can use pwgen
command:
$ pwgen -cns1
yEQ3Fieg
$ pwgen -cnsB1
4oTeNThy
$ pwgen -cnsBy1
gx&j/h4H
Please check the help of pwgen
for more information and flags.
As we set the KEYCLOAK_HOSTNAME=keycloak
we need to add 127.0.0.1 keycloak
into /etc/hosts
:
$ echo '127.0.0.1 keycloak' | sudo tee -a /etc/hosts
Now the Keycloak is running, and you can open up your web browser and go to https://keycloak:8080/, you should see something like this:
Then, log in with the entered username and password by clicking on “Administration Console”. This is the home page of Keycloak:
Note: in this deployment, we didn’t consider the database to persist data. But, in actual deployment, you should, https://www.keycloak.org/server/db.
Now, we need to create a new one for Tyk by going into the “client” menu and clicking on the “create” button:
Client ID: tyk-gateway
Client Protocol: openid-connect
Root URL: http://localhost:8000/
When you click on the “save” button, it creates the client and redirects you to the setting page of the client:
Here you can update the client’s settings, like Name
, Description
, and so on.
The essential parts that need to be changed are Access Type
and Valid Redirect URIs
:
Access Type: confidential
Service Accounts Enabled: On
Authorization Enabled: On
Valid Redirect URIs: http://localhost:8000/callback
After saving the changes, a new tab will appear on top of the page with the name “Credentials”, which we need to use in Tyk.
Remember that we need that Secret
in the Tyk config. We’ll get back to it in a few minutes.
Now go to the Mappers
tab and create a new mapper for aud
, which means “audience” related to the “client app”. In our case, it means Tyk.
Name: aud
Token Claim Name: aud
Claim Value: tyk-gateway
Claim JSON Type: String
Now, let’s create some roles, for example: Finance
and Delivery
by going to the Role tab.
Role name: Finance
Attributes: key=is_staff, value=true
Role name: Finance
Attributes: key=is_staff, value=false
Now, it’s time to create some users for this client.
Go to the “users” menu and create a new user:
Note: To make it simple for this tutorial, I put
username
and enabled theEmail Verified
checkbox.
By the Credentials
tab, set a password for the user and save it. Make sure the Temporary
flag is off.
Now let’s connect the user to the roles that we’ve created before:
Select your client from the Client Roles
and then from the appeared Available Roles
select delivery
and click on the Add selected
button.
Also, create another user for the finance
role, e.g., “finance-user1” and follow the same steps.
Now, the keycloak is ready to be tested! Let’s test to ensure we get the user’s token.
To find out the address to get the token, go to the home page of the Realm Settings (the menu has the same label), and click on OpenID Endpoint Configuration
in the Endpoint section:
You should see something like this:
It contains data in JSON format, and the URL that we need is in the token_endpoint
key, which in this case is http://keycloak:8080/realms/master/protocol/openid-connect/token address.
Now, let’s run the test to get an Access Token:
$ http --form POST http://keycloak:8080/realms/master/protocol/openid-connect/token client_id='tyk-gateway' client_secret='2pNW67LfoXC5KqsEtlBaTiodRCV5swGg' username='delivery-user1' password='1234' grant_type='password'
Reference: https://www.keycloak.org/docs/latest/authorization_services/
Note: the
client_secret
is the one I’ve mentioned we need for tyk.
So, it’s the result:
HTTP/1.1 200 OK
Cache-Control: no-store
Content-Type: application/json
Pragma: no-cache
Referrer-Policy: no-referrer
Set-Cookie: KEYCLOAK_LOCALE=; Version=1; Comment=Expiring cookie; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Max-Age=0; Path=/realms/master/; HttpOnly
Set-Cookie: KC_RESTART=; Version=1; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Max-Age=0; Path=/realms/master/; HttpOnly
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
content-length: 2226
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJHaXItNGgwTTdibElDZmhfbFNiMWdsek9lZHhSc09DZ2NKbTVIbUg5Q1pjIn0.eyJleHAiOjE2NTUwNjEzMTAsImlhdCI6MTY1NTA2MTI1MCwianRpIjoiNjZlMmVjY2ItYjg4OC00YThhLWEyNjUtZTIzNTZiZjQ0OTBmIiwiaXNzIjoiaHR0cDovL2tleWNsb2FrOjgwODAvcmVhbG1zL21hc3RlciIsImF1ZCI6WyJ0eWstZ2F0ZXdheSIsImFjY291bnQiXSwic3ViIjoiNmUyODliNzUtYTRmOS00ZmVmLWJhZGEtNmI3YTBiOGMyOGE0IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoidHlrLWdhdGV3YXkiLCJzZXNzaW9uX3N0YXRlIjoiYWZmNjI4YWUtN2U3MC00NzAyLTgzNTUtMWU0M2RhYTM5MjcwIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjgwMDAiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtbWFzdGVyIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7InR5ay1nYXRld2F5Ijp7InJvbGVzIjpbImRlbGl2ZXJ5Il19LCJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6InByb2ZpbGUgZW1haWwiLCJzaWQiOiJhZmY2MjhhZS03ZTcwLTQ3MDItODM1NS0xZTQzZGFhMzkyNzAiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwicHJlZmVycmVkX3VzZXJuYW1lIjoiZGVsaXZlcnktdXNlcjEifQ.dCJdSvzetzrem1Y5CcYOqvj09-0iai-TUU59rP_7ejEZK-VdPWiORZlmVpfMOHnv0u_SniPjmEacOxdIN_L4dWcCKUyUvAHtFl-qP0kTTR05cd7z91w2YDaQZz-y4-JTSAZG3x2gcU5j5sWheMxBDx1jFGsqSCdzDukUJE5Nuu_NtHvxgZ0JvkPNs9QOI6eqXGhDRyFKiblodHS_3LjyOg2WOcXzORi7-bTEhE3_SdDZYiy2qrMCtksJPZ4Q67v4IM3W6j9Y2BBjG_XVx-OnMdFHxtT4zezeTbkQE74sI2UzMigZ2QNt6TLDAejN7xhTDBxo8av6I58e4aJlwIj9oQ",
"expires_in": 60,
"not-before-policy": 0,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIwODYzZGZmMi01MmQxLTQxMDktYTEyMy0xMjg0ODAyNGMzOGIifQ.eyJleHAiOjE2NTUwNjMwNTAsImlhdCI6MTY1NTA2MTI1MCwianRpIjoiN2Y2ZWE5NTctMmJmNS00M2NjLWFlNTctMTU3NmM4ZDE4MGI0IiwiaXNzIjoiaHR0cDovL2tleWNsb2FrOjgwODAvcmVhbG1zL21hc3RlciIsImF1ZCI6Imh0dHA6Ly9rZXljbG9hazo4MDgwL3JlYWxtcy9tYXN0ZXIiLCJzdWIiOiI2ZTI4OWI3NS1hNGY5LTRmZWYtYmFkYS02YjdhMGI4YzI4YTQiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoidHlrLWdhdGV3YXkiLCJzZXNzaW9uX3N0YXRlIjoiYWZmNjI4YWUtN2U3MC00NzAyLTgzNTUtMWU0M2RhYTM5MjcwIiwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwic2lkIjoiYWZmNjI4YWUtN2U3MC00NzAyLTgzNTUtMWU0M2RhYTM5MjcwIn0.kOjebFQs-uHl_cd5giiZzvI-UTMU5pt0QaFQIaCxmcs",
"scope": "profile email",
"session_state": "aff628ae-7e70-4702-8355-1e43daa39270",
"token_type": "Bearer"
}
It contains all the required data.
If you decode the access_token
by https://jwt.io/ website, you’ll see what kind of information it does contain in the payload:
{
"exp": 1655061310,
"iat": 1655061250,
"jti": "66e2eccb-b888-4a8a-a265-e2356bf4490f",
"iss": "http://keycloak:8080/realms/master",
"aud": [
"tyk-gateway",
"account"
],
"sub": "6e289b75-a4f9-4fef-bada-6b7a0b8c28a4",
"typ": "Bearer",
"azp": "tyk-gateway",
"session_state": "aff628ae-7e70-4702-8355-1e43daa39270",
"acr": "1",
"allowed-origins": [
"http://localhost:8000"
],
"realm_access": {
"roles": [
"default-roles-master",
"offline_access",
"uma_authorization"
]
},
"resource_access": {
"tyk-gateway": {
"roles": [
"delivery"
]
},
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope": "profile email",
"sid": "aff628ae-7e70-4702-8355-1e43daa39270",
"email_verified": true,
"preferred_username": "delivery-user1"
}
As it’s clear, in the resource_access
, the user role is defined, and aud
contains the client name tyk-gateway
, which in the next post I’m going to use for a Tyk gateway.