Skip navigation
All People > Sascha Preibisch > Sascha Preibisch's Blog > 2016 > October > 04

Update - How to use SAML instead of JWT to integrate with an external Login-Server

After I was asked if SAML could be used instead of JWT and I have said yes, of course, I was asked several times to provide an example on how to do that. If have now implemented it and can share how it works.


First of all, whenever you want to start modifying something in OTK such as replacing JWT with SAML DO NOT do it in the OTK policies! That is way too complicated. Instead, create a sample API, copy the JWT parts out of the policy and put them into this new API. Use a tool such as Postman or SOAPUI and get that API working. Set sample values wherever you want. Once that is done, start replacing the bits and pieces that you want to swap until its working. After you did that, put those policy snippets into the OTK policies. Doing it this way makes troubleshooting way simpler.


What to do

You have to update policies at 7 locations. Luckily its almost always the same so you could create policy fragments and re-use them. In this guide we will simply copy and paste the same pieces. You have to have a private key to sign the SAML and you need a public cert for the encryption. In this example, I have created a private key and referenced it for both. In your environment you would import the public certificate of the login-server, the login-server would import your gateways public certificate to validate signatures.


First step:

/auth/oauth/v2/authorize, 1 single location: Create the initial SessionData variable

At line number 117 the policy uses an Encode Json Web Token: sign payload assertion. That has to be replaced with the following:


SAML instead of JWT


Line 118: Base64 encode the existing session data which is a JSON structure

Line 119: The SAML assertion requires a credential context. Since no user is involved and this SAML assertion is not about users but carrying data I used the URL of the login-server as username. The receiving API will check for that value.

Line 120: Create the SAML assertion using the private key odic (select one available in your system)

Line 121: Base64 encode the created SAML assertion XML message. Although we are not using JWT I kept the variable names to require less modifications. If you plan to not do JWT at all you may want to change the names to make it less confusing when looking at the policy in a few months (or even a few weeks) time

Line 122: URL Encode the string


For line 120, this is how you can configure it when going through the assertion creation wizard:

  • Step 2: SAML Version:  Version 2
  • Step 3: Issuer: select X.509 Subject Name, Issuer Value: Default
  • Step 4: SAML Statement: Attribute Statement
  • Step 6: Add the following attribute (and leave everything with default values):
    • Attribute Name: sessionData
    • Attribute Name Format: Basic
    • Attribute Value: ${sessionDataB64}
  • Step 7: Name Identifier: Include Name Identifier, Format: automatic, Name Identifier Value: From Credentials
  • Step 8: Subject Confirmation
    • Subject Confirmation Method: Bearer
    • Subject Confirmation Data: ${location_login_server}
  • Step 9: Conditions: Use Default Validity Period, Audience Restriction: ${location_login_server}
  • Step 10: Digital Signatures: Sign Assertion with an Enveloped Signature


This API is ready to go. As you can see, a few more lines then before but in the end very straight forward. Remember that you may want to configure your SAML assertion differently, this is just an example.


Next stop:

/auth/oauth/v2/authorize/login, 3 locations, Validate and create SAML

This API receives the SAML token created at the previous API. It has to validate it and, after the user has been authenticated, create a new one which has to be signed and partially encrypted. 2 of these 3 locations will use exactly the same policy snippet. In the original policy look for lines 50, 99 and 162. You can also search for JSON Web Token to find the JWT assertions.



In the original policy replace lines 50, 51 and 99, 100 with the following:


SAML instead of JWT


Line 51: Base64 decode the received SAML token. URL encoding is done by the gateway automagically. The created variable is of type XML

Line 52: Validate the SAML token

Line 53: Decode the session data into the variable sessionData of type application/json


For line 52 (and 104 after inserting these lines), this is how you can configure it when going through the assertion creation wizard:

  • SAML Version: Version 2.x
  • Attribute Statement:
    • Attribute Name: sessionData
    • Attribute Name Format: Basic
    • Attribute Value: Allow any non-empty value
  • Subject Confirmation: Method: Bearer, Recipient: ${location_login_server}, Check Validity Period
  • Name Identifier: Unspecified
  • Conditions: Audience Restriction: ${location_login_server}
  • Embedded Signature: Require Embedded Signature


As I said, configure this once and use it at both locations.



Now we have to create a SAML token. After modifying the 2 policy locations above we will continue at line 168 (which has been 162 in the original policy, Encode Json Web Token: sign & encrypt payload). As I said earlier, the SAML policy snippets are used multiple times.


Below is the new policy snippet to create the signed and encrypted SAML token. I will only explain the differences compared to the one at /auth/oauth/v2/authorize:

SAML instead of JWT


Line 169: Encode the variable sessionData instead of session 

Line 170, 171: Reference the variable location_consent_server instead of location_login_server

Line 172: Overwrite the variable issuedSamlAssertion to issuedSamlAssertion, use message type XML. This turns a string into a message which is needed at line 173

Line 173: Encrypt the sessionData. Please note that you may want to encrypt more parts of the message. In this example I only encrypt the sessionData itself


For line 173, this is how you can configure it when going through the assertion creation wizard:

  • Elements: /saml2:Assertion/saml2:AttributeStatement/saml2:Attribute/saml2:AttributeValue
  • Encryption method: choose one that is good for your environment. I did not worry about it for this example and used this one:
  • Specify certificate: from the menu select the public certificate your login server provided for encryption. It must have been imported into the gateway first. In my case I selected my example private key as the source
  • Do a right-click and configure the target message to be: issuedSamlAssertion


Last stop

/auth/oauth/v2/authorize/consent, 3 locations, just one new implementation

This policy has to validate the SAML and decrypt the sessionData element. It also has to create it. 


Search for the occurences of Decode Json Web Token. They will come in pairs, one is decrypting the JWT, the other is checking the signature. Replace both pairs to look like this:

SAML instead of JWT


As you may see it is the same as on lines 50, 51 that we have created earlier. The only addition is the decrypt assertion on line 49. That assertion only requires one piece of configuration in the dialog:

  • XPath to encrypted element: /saml2:Assertion/saml2:AttributeStatement/saml2:Attribute/xenc:EncryptedData

Also, do a right-click and configure the target message to be: samlBearer


The remaining JWT assertion is Encode Json Web Token: sign & encrypt payload. Replace that line (64 originally) with this:

SAML instead of JWT


Guess what? It is exactly the same as before! Lines 67 - 74 match lines 168 - 175 further up.



You can now integrate with an external login-server using SAML instead of JWT. Use this example and apply any modifications as you need them. In addition to API tests I have used the Safari browser with the OTK browser based test clients to verify that its working. I had some trouble using Chrome (displaying an empty screen) but I did not want to postpone this post cause of debugging purposes. Maybe you can let me know what the problem is :-)


I hope this helps!

Many of our customers have asked us to support easy integration of OTK with an external, existing Login-Server. Until now it was easy to connect to an external IDP but that is not always sufficient.


Well, we have listened and have updated OTK-3.5.00 to support this scenario. This blog post describes how it works.


Starting point: fresh install of OTK-3.5.00


With this version OTK introduces new API’s. In addition to /auth/oauth/v2/authorize the API’s /auth/oauth/v2/authorize/login and /auth/oauth/v2/authorize/consent have been introduced:

  • …/login handles the user authentication
  • …/consent handles the users consent decision (grant or deny)

As in older versions, and in compliance with RFC 6749 (OAuth 2.0) , clients will send initial authorization requests to /auth/oauth/v2/authorize. OTK runs through a validation process and either fails the request or continues.


In the case of a valid request OTK will create a session and keeps track of that. But in addition to that OTK also creates a signed JWT (JSON Web Token). That signed JWT is called sessionData and includes values associated with the initial client request. It is bound to the session that OTK keeps track of.


The response for the initial request will be a redirect to /auth/oauth/v2/authorize/login. That API receives the session identifier (sessionID) and also the session JWT (sessionData). OTK validates the JWT signature, checks if it is associated to the given sessionID and, if valid, responds with a default login page.


Once the user provided his credentials OTK validates them. If valid, OTK updates the JWT and signs it again. This time it also encrypts it due to the now sensitive content. An Auto-Form-POST to /auth/oauth/v2/authorize/consent is returned. That API displays a consent screen. It includes the client’s name and the requested SCOPE. Once the user decided to either grant or deny the request a form POST back to …/consent is executed. This time OTK takes the decision, issues an authorization_code or access_token (if granted) and redirects back to the client.


How do I leverage my Login-Server?


Well, almost very simple. But for sure … simple. There are only a few steps required. A few requirements for the external Login-Server, a few for OTK.


The external Login-Server


  • MUST be able to validate and create signed & encrypted JWT using a shared secret
  • MUST be able to authenticate the user
  • MUST be able to redirect to /auth/oauth/v2/authorize/consent and include all required parameters



  • OTK uses two shared secrets. One to generate/validate the JWT signature, one to encrypt/decrypt the JWT. These are custom values. To leverage the external Login-Server those secrets have to be configured on that server
  • OTK has to be configured to redirect to the external Login-Server


That’s it!


If you now say: well, my Login-Server does not support JWT then this is the right opportunity to implement it. There are open source libraries available that make it very easy to do so. For an evaluation project I was able to implement JWT signing and encryption in Java within a few hours.


Sascha, I want more details!


Before you ask, here are details you need to know. They are all documented but here I will mention the most important bits and pieces.

Here is a simple but hopefully useful graphic of the possible flows:


OTK integrating with an external server


First things first


Configure the shared secrets in OTK and your external Login-Server:

  • Use the policy manager and open the policy OTK Authorization Server Configuration and set the values for “otk_session_secret” and “otk_session_secret_encryption”
  • “otk_session_secret” is used to create and validate the JWT signature
  • “otk_session_secret_encryption” is used to encrypt/decrypt the JWT


Configure OTK to redirect to your external Login-Server. In this example the external server will handle the login step only, not the consent decision:

  • Use the policy manager and open the same policy as before. Configure the variables “host_login_server” and “path_login_server”
  • “host_login_server”: it has to follow this pattern: https://your_exernal_login_server:port
  • “path_login_server”: this has to be the path that is the target for the redirect, i.e.: by default: /auth/oauth/v2/authorize/login


Configure your external Login-Server to redirect to OTK after the user has been authenticated. The target is https://your_gateway:port/auth/oauth/v2/authorize/consent


TIP: When working in the policy manager ALWAYS turn on policy comments and assertion line numbers!

Requests, back and forth

In this example a client (client_id=xyz, name=ExampleApp) has been registered in OTK and is now using the authorization_code flow. The external Login-Server is hosted on


GET /auth/oauth/v2/authorize?






OTK receives the request and validates the client_id, the redirect_uri and the SCOPE. (NOTE: in OTK only SCOPE’s that have been registered for that client can be requested).


If valid OTK responds with a redirect:


Status: 302,





  • action: “display” indicates that a login screen has to be shown. The other option would be “login” for the case of a client using “prompt=none” and “id_token_hint=an-id_token” to indicate that the user is already logged in
  • sessionID: this value is opaque to the external Login-Server and has to be passed back to OTK after the user was authenticated
  • sessionData: that JWT’s signature has to be validated


The JWT content is a JSON message like the example below. It has three main sections:



   "session": {



   "request_consent": {

       "client_name": "ExampleApp",

       "scope_verified": "opened email"


   "request_parameters": {

       "display": "page",

       "prompt": "login consent",

       "id_token_hint": "",

       "acr_values": "",

       "client_id": "xyz",


       "scope": "openid email"



  • session: the session to which the JWT is bound including the expiration time in seconds
  • request_consent: the content to be displayed on the consent page (client name, SCOPE)
  • request_parameters: the parameters that were received in the initial client request


Once received extract the "exp"(irationtime) from the section "session" and verify that it has not expired. Also verify that "sessionID" in the same section matches the given "sessionID" parameter. Oh, and certainly verify the JWT signature! Continue if those validations were successful.


In the simple case your server would now display an authentication page. The user authenticates (however you want him to authenticate!) and your server validates the credentials.


Now your server has to update the JSON message in the section "session". And because some of the values are sensitive (e.g.: salt, which is required to create the ‘sub’ value in the id_token) it has to be signed and encrypted. Do the following:

  • current_user_consent: set it to "none"
  • current_username: set it to the username
  • current_user_role: set it to an appropriate value, e.g.: “admin”, “user”, “visitor” whatever is associated with that user. If no role exists set it to “user”. The roles "admin" and "user" are handled by default in OTK.
  • current_user_acr: leave it untouched or set it to a value that is valid in your environment and represents the used authentication class
  • current_user_authTime: set it to the time when the user authenticated. Most likely to “now” (in seconds, 10-digits)
  • salt: set it to a value that is only known in your environment and associated with the authenticated user. It will be used by OTK but will never be exposed


Once this is done use the updated JSON message, sign end encrypt it. Create an Auto-Form-POST back to OTK. It has to include the following parameters:


POST https://your_gateway:port/auth/oauth/v2/authorize/consent




OTK will receive the message and will do the following:

  • verify that “consent” is the appropriate action
  • verify that sessionID has not expired, is known and has not been used before
  • decrypts sessionData and validates the signature
  • verifies that sessionData is bound to the given sessionID


After that OTK will extract the given values and continues its flow. The user will be prompted with a consent page.



This blog post should enable you to get started with an integration. Always remember that it is mostly about handling the session JWT and sending it to the right location.


For questions please leave a comment.