09 December 2015

Fediz with OpenID Connect Support and WS-Federation Bridge (1/2)

I'm currently engaged for a big company to provide a solution that allows this company to offer various (REST) services to their partners while these services are hosted and maintained by the company but users can login to these services with accounts managed within their own partner network.

This solution should work for Web-Portals, Mobile Apps & Desktop Applications.

First I was skeptical if it will be possible to find one solution fitting all theses different use cases. But I think I actually did find a very interesting solution. In this post I'll explain the overall architecture of this solution. In my next posts I'll tell you how to get a Liferay Web-Portal integrated as well as a mobile App based on Android.

WS-Federation normally uses SAML Tokens for user authentication. This is fine for container based security solutions, when the user wants to login to a web-portal. But modern web applications (e.g. AJAX based) tend to be executed primarily in the Browser, invoking REST backend services directly from within the Browser.
Handling XML based tokens (incl. XML signature validation) is just a too heavy burden for this type of applications. Also handling lifetime issues with SAML Token could require a Token exchange with an STS. But an STS only provides a SOAP interface according to WS-Trust. It is not feasible for a AJAX Web Application to handle SOAP communication including XML security. Browser based applications should be light-weight and thus they prefer talking to REST services.

Token Types & Token Handling

JSON Web Tokens (JWT) are similar to SAML tokens but instead of being XML based they are provided in a JSON format. Since JSON is the preferred document format for REST services, they fit much better to a REST Service compared to a XML based SAML Token.

One step closer to the solution could be to provide a STS that can issue JWT tokens as well as SAML tokens. This could help to exchange SAML token to JWT token and vice versa in cases when you need both. SAML for SOAP services and JWT for REST services. Colm O hEigeartaigh has already done some improvements to the CXF STS to support JWT tokens at the STS.

Instead of requesting a SAML Token with the following TokenType http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0 you can now also request a JWT token with this TokenType urn:ietf:params:oauth:token-type:jwt.

This way you can request or even exchange JWT tokens simply by setting the expected TokenType value. When sending a SAML or JWT token OnBehalfOf your token request you can exchange one format for the other.
What is currently missing to bridge both token types for SOAP and REST scenarios would be a REST interface for the STS. This would make it a lot easier for REST clients to interact with an STS. Since there are no standards for a STS REST API, I'll design a REST interface by myself discuss it with the CXF community and then add it to the STS soon.

OpenID Connect

I would see OpenID Connect as the successor standard for WS-Federation which was the successor standard of the SAML Web-Profile. You can see that the OIDC sequence flow is still similar to the prior protocols but that it also learned and improved a lot from the older standards.

OIDC is also based on the OAuth2 standard and thus inherits also all benefits and drawbacks of this protocol.

Many big internet companies support OpenID Connect like Google, Facebook, Twitter, etc..
OpenID Connect is related but not equal to OpenID on which some of the bigger companies dropped already the support for (like Google).

Simple Sequence Flow

OpenID connect is based on OAuth2 and thus provides several sequence flows. Which one you will choose will depend on your circumstances. In this blog I'll focus on the authorization code flow only.

(1) The user invokes the WebPortal (same would apply if the user invokes a REST Service).
GET https://demo.portal.com/webPortal/index.html HTTP/1.1
Host: demo.portal.com
to be continued...
(2) Since there is no existing session with the user, the server response with a redirect to the OIDC Server (line breaks added to improve readability).
HTTP/1.1 303 See Other
Location: https://your.oidc-server.com/idp/login
              ?client_id=Ro2hJVtj94oWLw
              &scope=openid
              &response_type=code
              &redirect_uri=https%3A%2F%2Fdemo.portal.com%2FwebPortal%2Foidc
              &state=a8a4ee9e8061a34f93539635ce02e32
              &nonce=1d5c428ffbff3eed95721339e67c56e8c2aa4add6bb493e436249578c81f88
The user will see a login screen for authentication.
(3) After successful login the user will be redirected back to the provided redirect_uri with an authorization code:
HTTP/1.1 303 See Other
Set-Cookie: JSESSIONID=55638DC5FF717DD730B72978B70F088E; Path=/idp/; Secure; HttpOnly
Cache-Control: private
Expires: Thu, 01 Jan 1970 01:00:00 CET
Date: Thu, 19 Nov 2015 13:39:08 GMT
Location: https://demo.portal.com/webPortal/oidc
              ?state=a8a4ee9e8061a34f93539635ce02e32
              &code=a1a822cd99da976f47c8a7218ae212
(4) This code can now be used by the service provider (web portal) to get a JWT token. For this reason the service provider needs to authenticate against the token endpoint with its client_id & client_secret within the Authorization header, as well as the code value and redirect_uri.
POST https://your.oidc-server.com/idp/oauth2/token
Host: your.oidc-server.com
Authorization: Basic Um8yaEpWdGo5NG9XTHc6RUFpemlWUU5PWVpmaHN5WGlSNmpxUQ==

              grant_type=authorization_code
              &code=a1a822cd99da976f47c8a7218ae212
              &redirect_uri=https%3A%2F%2Fdemo.portal.com%2FwebPortal%2Foidc
As a result the token endpoint of the OIDC IDP will return the JWT Token as well as an access code. The access code will not be needed in this first scenario but is just part of the usual Oauth2 flow.
HTTP/1.1 200 OK
Content-Type: application/json

{
 "access_token": "a8de6a544738c9e9036318ee78dfe7",
 "token_type": "Bearer",
 "expires_in": 3600,
 "scope": "openid",
 "id_token": "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJib2IiLCJhdWQiOiJVVUY5RmRzNjdubnA2QSIsImlhdCI6MTQ0OTY3NDMyNywiZXhwIjoxNDQ5NzM0MzI3LCJnaXZlbl9uYW1lIjoiQm9iIiwiZW1haWwiOiJib2J3aW5kc29yQHJlYWxtYS5vcmciLCJmYW1pbHlfbmFtZSI6IldpbmRzb3IiLCJuYW1lIjoiQm9iIFdpbmRzb3IiLCJub25jZSI6IjFkNWM0MjhmZmJmZjNlZWQ5NTcyMTMzOWU2N2M1NmU4YzJhYTRhZGQ2YmI0OTNlNDM2MjQ5NTc4YzgxZjg4IiwiaXNzIjoiYWNjb3VudHMuZmVkaXouY29tIiwiYXRfaGFzaCI6InZvc1h4a1lhcW1aYmJiTWlVdWdYRUEifQ.VQB4QfI1nrka5cfgq8aOGyT0iv3EEE7BBETIzQHbee1t9BDr8wPNV55pGY1YqU6F5C9KIkWiIGLz8MBlKwQVfU7FfzXqm4gEF2zbq4i_LyL-f_RW38-id4VmfF3n6ybWDqdmLLXagFL2UurTdIuGWxbZVW_bgnuIc5dJfnQ_b2wi9hxoL-1u4PPufDLfTLBYRtP5zaedR01me9T5i6PCn2jZSdvfd9304Jf6MOf98Cvv-faeZ87ilUrd5k3q4tQl6dIL3IefPBTfbR8Ni69viD29A-PvtKLquQcGp6p3NEXnwOYF7c72ZsjuZI2pcItoeBeo2Whz9Ni21vfWsd3Iaw"
}
The id_token contains three values of interest base64 encoded and separated by a dot '.':
eyJhbGciOiJSUzI1NiJ9

eyJzdWIiOiJib2IiLCJhdWQiOiJVVUY5RmRzNjdubnA2QSIsImlhdCI6MTQ0OTY3NDMyNywiZXhwIjoxNDQ5NzM0MzI3LCJnaXZlbl9uYW1lIjoiQm9iIiwiZW1haWwiOiJib2J3aW5kc29yQHJlYWxtYS5vcmciLCJmYW1pbHlfbmFtZSI6IldpbmRzb3IiLCJuYW1lIjoiQm9iIFdpbmRzb3IiLCJub25jZSI6IjFkNWM0MjhmZmJmZjNlZWQ5NTcyMTMzOWU2N2M1NmU4YzJhYTRhZGQ2YmI0OTNlNDM2MjQ5NTc4YzgxZjg4IiwiaXNzIjoiYWNjb3VudHMuZmVkaXouY29tIiwiYXRfaGFzaCI6InZvc1h4a1lhcW1aYmJiTWlVdWdYRUEifQ

VQB4QfI1nrka5cfgq8aOGyT0iv3EEE7BBETIzQHbee1t9BDr8wPNV55pGY1YqU6F5C9KIkWiIGLz8MBlKwQVfU7FfzXqm4gEF2zbq4i_LyL-f_RW38-id4VmfF3n6ybWDqdmLLXagFL2UurTdIuGWxbZVW_bgnuIc5dJfnQ_b2wi9hxoL-1u4PPufDLfTLBYRtP5zaedR01me9T5i6PCn2jZSdvfd9304Jf6MOf98Cvv-faeZ87ilUrd5k3q4tQl6dIL3IefPBTfbR8Ni69viD29A-PvtKLquQcGp6p3NEXnwOYF7c72ZsjuZI2pcItoeBeo2Whz9Ni21vfWsd3Iaw
After base64 decoding the first value will tell us which algorithm has been used to secure the ID token:
{"alg":"RS256"}
And the second value will contain the actual JWT token:
{
 "sub": "bob",
 "aud": "UUF9Fds67nnp6A",
 "iat": 1449674327,
 "exp": 1449734327,
 "given_name": "Bob",
 "email": "bobwindsor@realma.org",
 "family_name": "Windsor",
 "name": "Bob Windsor",
 "nonce": "1d5c428ffbff3eed95721339e67c56e8c2aa4add6bb493e436249578c81f88",
 "iss": "accounts.fediz.com",
 "at_hash": "vosXxkYaqmZbbbMiUugXEA"
}
The third parameter will contain the digital signature (binary value) thus I cannot display it here.

After consuming the authorization code the user will be redirected back to the initially requested resource
HTTP/1.1 303 See Other
Location: https://demo.portal.com/webPortal/index.html

OpenID Connect with WS-Federation Login

Now after understanding the basic OpenID Connect flow lets extend this scenario by performing the login not directly at the OIDC service but instead using the Fediz-Plugin at the OIDC Service to redirect the user according to WS-Federation to the Fediz-IDP for user authentication. After successful login at the Fediz-IDP the user will be send back to the OIDC with a SAML token. This token can then be mapped to a JWT token for the OpenID connect login process.


(1) In the first step the user invokes the web portal.
(2) Since now existing session exists the user will be redirected to the OIDC Service according to OpenID Connect.
(3) An Apache Fediz Plugin is active on the OIDC Server. Since now previous session exists the Fediz Plugin will redirect the user to the RP-IDP according to WS-Federation.
(4) The user will see a login screen and will be redirected back to the OIDC server after successful authentication with a SAML token.
(5) The Fediz Plugin will store the SAML Token. OIDC will provide the requested code from step (2).
(6) The Web Portal can now exchange this code according to OAuth2 to get the JWT Token (transformation of the SAML token), as well as an access token to invoke another backend service.
(7) The REST Service will be invoked with the access token received in the previous step
GET https://my.services.com/backend/service
Host: my.services.com
Authorization: Bearer a8de6a544738c9e9036318ee78dfe7
(8) The REST Service will send the access token to the OIDC to ensure that this code is valid. (Unfortunately it is not defined within the standard how this validation should happen)

In my second part of this post, I'll explain how to setup a demonstrator for the above described use case.

Federated SSO with WS-Federation & OpenID Connect

After understanding OpenID Connect and WS-Federation Login I'll extend this scenario by one more degree of complexity thus providing us the flexibility which we discusses at the beginning to provide a SSO solution not just within one company but for multiple partners at the same time.

(1) A user from a partner company invokes the web portal.
(2) Since now existing session exists the user will be redirected to the OIDC Service according to OpenID Connect.
(3) An Apache Fediz Plugin is active on the OIDC Server. Since now previous session exists the Fediz Plugin will redirect the user to the RP-IDP according to WS-Federation.
(4) Since no existing session exists at the RP-IDP, the RP-IDP will perform a Home-Realm discovery and after that redirects the user to its own Requestor IDP (more details about this in the next section).
(5) After successful login the Requestor IDP will issue a SAML Token for the user and redirect the user back to the RP-IDP.
(6) The RP-IDP will to do a Claims or Identity Mapping and after that issue a new SAML token applicable for the OIDC Service and redirects the user back to the OIDC service.
(7) The Fediz Plugin at the OIDC service will store the SAML Token and OIDC will provide the requested code from step (2).
(8) The Web Portal can now exchange this code according to OAuth2 to get the JWT Token (transformation of the SAML token), as well as an access token to invoke a backend service.
(9) The REST Service will be invoked with the access token received in the step (8).
(10) The REST Service will send the access token to the OIDC to ensure that this code is valid. (Unfortunately it is not defined within the standard how this validation should happen)

Home Realm Discovery

If your web portal shall be used by users from multiple realms (e.g. partner networks), you need to find a way to discover the home realm of each user. The OpenID Connect Standard provides a parameter login_hint for this purpose whereas WS-Federation Standard offers a whr parameter for this purpose.

There are several ways to do a home realm discovery:
  1. Dedicated URLs
    You can provide different URLs for each user group. This can be done by adding the login_hint parameter directly to the URL which the user is invoking (e.g. link within a different portal which the user usually access first). Also very famous is the usage of different domain names with a reverse proxy in place which adds the login_hint / whr parameter.
  2. E-Mail Address
    If there is just a single page which will be used by all user groups it is also a good choice to ask the user for his/her E-Mail address and then use this as the  the login_hint.The OIDC service could use the domain name of this E-Mail Address to set it as the whr parameter for home realm discovery at the RP-IDP. The RP-IDP would need a mapping table of domain names and Requestor IDP URLs to send the user to its home IDP.
    One disadvantage of this solution is that the user will most likely be required to enter his/her E-Mail address twice. First at the portal to initiate the home realm discovery and later for the login at the Requestor IDP. This is because WS-Federation does not provide a felxible login_hint but instead only a more static whr parameter which will be the same value for all users within one group. You can improve the user experience if you use cookies to remember the selected home realm of a user until a user initiated logout takes place.
  3. IP Range
    IP ranges can also be used for home realm discovery but are usually not a good choice because users will not just login from a company network with a static IP but also from mobile devices with a dynamic IP range.
  4. Miscellaneous
    Any other way to securely identify the home realm of a user would also be acceptable, but will usually require custom development efforts to the services.

6 comments:

  1. Hi Jan
    In my case I need to use an OpenId connect Server as IDP and provide an SAML assertion to the Web Portal.
    Do you see some limits ?

    ReplyDelete
    Replies
    1. If I understand you correctly you want to authenticate your user against an OpenID Provider and finally get a WS-Federation login with a SAML token to your web portal?
      This should absolutely be possible with the Fediz IDP (only). You will not need the OIDC Service extension for that since the IDP supports OIDC login.

      Delete