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.comto 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=1d5c428ffbff3eed95721339e67c56e8c2aa4add6bb493e436249578c81f88The 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%2FoidcAs 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-PvtKLquQcGp6p3NEXnwOYF7c72ZsjuZI2pcItoeBeo2Whz9Ni21vfWsd3IawAfter 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 parameterlogin_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:
- Dedicated URLs
You can provide different URLs for each user group. This can be done by adding thelogin_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 thelogin_hint
/whr
parameter. - 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 thelogin_hint
.The OIDC service could use the domain name of this E-Mail Address to set it as thewhr
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 felxiblelogin_hint
but instead only a more staticwhr
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. - 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. - 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.
Hi Jan
ReplyDeleteIn 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 ?
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?
DeleteThis 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.