Technical Articles
Bring your self-made user JWT with Keycloak OIDC.
Good to know:
Disclaimer:
|
Putting it all together
What does it mean: Bring your own user JWT with Keycloak as OpenID Connect provider ?Well, it means that:
|
ad1. Create your own user JWT.
JsonWebToken implementation for node.js http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html describes the self-issued JWT as follows:
Please consider SAP BTP destination service will reject:
|
Let’s implement it with a very simple nodejs function
Good to know:
|
const jwt = require('jsonwebtoken');
const cert = '-----BEGIN CERTIFICATE-----\nMIIFGzCCAwMCBGBb1dwwDQYJKoZIhvcNAQELBQAwUjELMAkGA1UEBhMCVVMxDDAK\nBgNVBAoMA1NBUDEVMBMGA1UECwwMY
..........................(truncated)............................
URdaHVX6B270SUDiP6TDPApn9E+IaISzPRpk\nXT6c0QNVYg37DBU/qhSN\n-----END CERTIFICATE-----\n';
const key = '-----BEGIN PRIVATE KEY-----\nMIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDFz/eQv30tj5oC\nLjT1Im7OtVAVo6mB/wQbEpbOh3LSI
..........................(truncated)............................
LSxBTGdiZznqgLKnImxU1WDSA2xlKJy7J\nAwx8lLYgANSJ7qkKPgPR/t5ZHrx/plY=\n-----END PRIVATE KEY-----\n';
function generateAccessToken(username) {
return jwt.sign( {username: username }, key, { algorithm: 'RS256', expiresIn: '1d' } );
}
module.exports = {
main: async function (event, context) {
switch (event.extensions.request.path) {
case '/generateAccessToken' : {
return generateAccessToken('TOTO');
}
}
}
}
Find the function name and its running pod and then create a port forwarding tunnel for localhost execution (please consider the function generateJWT is run in total isolation on a kyma cluster)
$ kubectl get pods -n default --kubeconfig ~/.kube/kubeconfig.yaml
NAME READY STATUS RESTARTS AGE
generateJWT-l6kfp-6d96778ccb-dw9dh 2/2 Running 0 2d
$ kubectl port-forward pod/generateJWT-l6kfp-6d96778ccb-dw9dh 8080:8080 -n default --kubeconfig ~/.kube/kubeconfig.yaml
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
Let’s call our function locally with the /generateAccessToken endpoint as follows:
Summary
URL: http://localhost:8080/generateAccessToken
Status: 200 OK
Source: Network
Address: ::1:8080
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IlRPVE8iLCJpYXQiOjE2MjU5MTcwMjAsImV4cCI6MTYy
..........................(truncated)............................
5W-Gjsp9oCIpSRUM0Rtyw6DefTf7pybQZPR6gxKUpvzpzfCnnCc4Ci0RWPllmTJGe2DVjTijnGvEGFbnQr4ibU4E
header:
{
"alg": "RS256",
"typ": "JWT"
}
payload:
{
"username": "TOTO",
"iat": 1625917020,
"exp": 1626521820
}
verify signature:
RSASHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
ad2. add our own x509 key pair into one the Keycloak’s realms
This must be the same x509 key pair that was used to create a self-issued JWT token.
ad3. Get OIDC provider metadata.
ad4. Retrieve jwks_uri (a pointer to jwks)
ad5. Find destination (SAMLAssertion)
As aforementioned, we shall have destination service generate a signed saml assertion with our self-issued user identity against a mock-up destination.
Let’s use the self-issued JWT token in the x-user-token header of find destination call.
|
Follows a mock-up destination definition and the json output of a find destination call:
{
"owner": {
"SubaccountId": "SubaccountId",
"InstanceId": null
},
"destinationConfiguration": {
"Name": "QUOVADIS-BRING-YOUR-OWN-JWT",
"Type": "HTTP",
"URL": "https://api-TOTO/oauth/token",
"Authentication": "SAMLAssertion",
"ProxyType": "Internet",
"KeyStorePassword": "<KeyStorePassword>",
"audience": "www.audience.com",
"authnContextClassRef": "urn:oasis:names:tc:SAML:2.0:ac:classes:PreviousSession",
"clientKey": "clientKey",
"KeyStoreLocation": "toto.p12",
"nameIdFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
"x_user_token.jwks_uri": "https://keycloak.<cluster>/auth/realms/ateam-isveng/protocol/openid-connect/certs",
"tokenServiceURL": "https://api-TOTO/oauth/token",
"userIdSource": "username"
},
"certificates": [
{
"Name": "toto.p12",
"Content": "MIIQeAIBAzCCEDIGCSqGSIb3DQEHAaCCECMEghAfMIIQGzCCCggGCSqGSIb3DQEHAaCCCfkEggn1MIIJ8TCCCe0GCyqGSIb3PG/HcO5xW0ai3ZkwPTAhMAkGBSsOAwIaBQAEFMyS26dKft/dWRSIVg/SQvDnQmaSBBQTST14Lse+rKA3igKg4Q7gzIB0mAICBAA=",
"Type": "CERTIFICATE"
}
],
"authTokens": [
{
"type": "SAML2.0",
"value": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDI6QXNzZXJ0aW9uIHhtbG5zOnNhbWwyPSJ1cm46C9zYW1sMjpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PC9zYW1sMjpBc3NlcnRpb24+",
"http_header": {
"key": "Authorization",
"value": "SAML2.0 PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDI6QXNzZXJ0aW9uIHhtbG5zOnNhbWwyPSJ1cm46b9zYW1sMjpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PC9zYW1sMjpBc3NlcnRpb24+"
}
}
]
}
Good to know:
|
Conclusion
We were able to create a self-issued user JWT token with our own username claim. And then we were able to generate a signed saml assertion… Of course if we were to use this signed saml assertion as a user identity bearer, both the username claimed would have to exist in the target system and the target system would need to have either saml bearer assertion provider or oauth provider set up with the x509 certificate that was used to sign the assertion. |
__________
Appendix
Good to know:
- Keycloak is running on kyma runtime as well.
- You may use it as a tool to help generate the jkws array that contains your key pair.
Get OIDC provider metadata.
https://keycloak.<cluster>/auth/realms/ateam-isveng/.well-known/openid-configuration
{
"issuer": "https://keycloak.<cluster>/auth/realms/ateam-isveng",
"authorization_endpoint": "https://keycloak.<cluster>/auth/realms/ateam-isveng/protocol/openid-connect/auth",
"token_endpoint": "https://keycloak.<cluster>/auth/realms/ateam-isveng/protocol/openid-connect/token",
"introspection_endpoint": "https://keycloak.<cluster>/auth/realms/ateam-isveng/protocol/openid-connect/token/introspect",
"userinfo_endpoint": "https://keycloak.<cluster>/auth/realms/ateam-isveng/protocol/openid-connect/userinfo",
"end_session_endpoint": "https://keycloak.<cluster>/auth/realms/ateam-isveng/protocol/openid-connect/logout",
"jwks_uri": "https://keycloak.<cluster>/auth/realms/ateam-isveng/protocol/openid-connect/certs",
"check_session_iframe": "https://keycloak.<cluster>/auth/realms/ateam-isveng/protocol/openid-connect/login-status-iframe.html",
"grant_types_supported": [
"authorization_code",
"implicit",
"refresh_token",
"password",
"client_credentials",
"urn:ietf:params:oauth:grant-type:device_code",
"urn:openid:params:grant-type:ciba"
],
"response_types_supported": [
"code",
"none",
"id_token",
"token",
"id_token token",
"code id_token",
"code token",
"code id_token token"
],
................................
}
Retrieve jwks_uri (a pointer to jwks) from the above metadata.
https://keycloak.<cluster>/auth/realms/ateam-isveng/protocol/openid-connect/certs
{
"keys": [
{
},
{
"kid": "QtV2kV6VfnvJpRHCHFVXjDPFKydXWVHjuz4XIE0xxxx",
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"n": "xc_3kL99LY-aAi409SJuzrVQFaOpgf8EGxKWzody0iPIf39NH8CzCf7kN52B4sKrBJU7ygNIdAebZu_cP2dRQcf7646q2yr0BBVUP8x92FRLHQT5J-Au4jL7psDBAq4G4TUxTgAFYqrAyQCUSlfW1VoRxzCMYJVtV0HUpC0yyV-RsaXQ6T6RNJTmk1FpFpgI2wO_gtaRSdVxA1cMpzvfsfiDhK-gEDDhoYQCYWzsLxxxx",
"e": "AQAB",
"x5c": [
"MIIFGzCCAwMCBGBb1dwwDQYJKoZIhvcNAQELBQAwUjELMAkGA1UEBhMCVVMxDDAKBgNVBAoMA1NBUDEVMBMGA1UECwwMYXRxxxx"
],
"x5t": "DQMOdr9ITZxhd6NYI7l8y6Lxxxx",
"x5t#S256": "NINub7bHeCqT1mEJiQcUdbJtJgSmTeQhw48DGAJxxxx"
},
{
}
]
}
JSON Web Key Set Properties.
alg |
The specific cryptographic algorithm used with the key. |
kty |
The family of cryptographic algorithms used with the key. |
use |
How the key was meant to be used; sig represents the signature. |
x5c |
The x.509 certificate chain. The first entry in the array is the certificate to use for token verification; the other certificates can be used to verify this first certificate. |
n |
The modulus for the RSA public key. |
e |
The exponent for the RSA public key. |
kid |
The unique identifier for the key. |
x5t |
The thumbprint of the x.509 cert (SHA-1 thumbprint). |
Additional resources.
Usage of external JWT in CDS Services Mapping of SAML attributes with XSUAA JWT in Cloud Foundry Easy path to productive use with the free tier model for SAP Business Technology Platform |