Perfecting the Login Experience with Liberty OAuth2 and App ID
13 June 2019
4 min read
The login experience is the end user’s first impression of an application. It should be simple to use and—most importantly—error free.

The login experience is the end-user’s first impression of an application. It should be simple to use and—most importantly—error free.

This technical article addresses the latter by detailing Liberty configuration that produces the ideal OAuth2 login experience, using IBM Cloud App ID as an example authentication provider.

Prerequisites and overview

Before reading this article, it is recommended to review the following:

In the above flow, the protected webapp is served by an application server—in this case, Websphere Liberty. The appserver is responsible for handling the redirect to the OAuth2 authentication provider, then the exchange of the user’s authorization_code  for OAuth2 authentication tokens (JWT), while additionally establishing login and session cookies as required to maintain a secure state during the process.

If you were to inspect the default cookies created and destroyed during Liberty’s OAuth2 authentication process, you would identify at least the following:

  • WASOidcState *: Authentication cookie containing a once-off state
  • WASReqURLOidc *: Authentication cookie containing the requested application url, for redirect after authentication is successful
  • LTPAToken2 : Session cookie for an established authenticated session

*The former two cookies are appended with a randomly generated nonce

Authentication flow timeout

Liberty’s authentication cookies expire by default after just seven minutes, governed by the openidConnectClient property authenticationTimeLimit. These cookies are established when the end-user first requests the protected application URL, as seen in the OAuth2 authentication flow. Functionally, this means that after first attempting to access the application, the end-user has seven minutes to enter their credentials (including invalid attempts) and be redirected back to Liberty.

For example, if they accessed the application home page as an unauthenticated user and waited 10 minutes before successfully entering their valid credentials, Liberty will reply by default with an unfriendly generic 401 or 500 error. The server log would indicate something similar to: 

[ERROR ] CWWKS1750E: A request to [https://localhost:9044/oidcclient/redirect/MyRP] is not valid. 
A required cookie with a name that begins with WASReqURLOidc is missing. 
The host name that is used to access the client might not match the name that is registered at the provider. 
A response code of 500 is returned.

Fundamentally, whenever these cookies have expired, the authentication flow cannot succeed.

To counter this, the authentication time limit should be set to the maximum period that you expect your users to idle during the authentication flow without requesting the protected application URL. App ID’s standard login page, for example, expires after 30 minutes. Therefore, in a simplistic example, an authenticationTimeLimit of “30m” could be reasonable.

However, a distracted user might enter their credentials incorrectly, refresh the login page or otherwise without ever interacting with Liberty. This would require an authentication time limit increase to an hour or two. Note that for security and functional reasons, this limit should remain as low as possible while still supporting your intended use cases.

Session failover on Liberty with OAuth2

If your application is deployed in a highly available environment with more than one application server node, you will need a configuration strategy for session failover. This will improve the experience for end-users in the event a Liberty node were to stop responding.

For example, if any users had an LTPA session token (cookie) issued by LibertyNodeA , which subsequently went offline, a load balancer could direct those users to LibertyNodeB . However, all of those users would automatically be logged out; their LTPA token is not valid on any other Liberty node by default.

We can improve that experience— Liberty does support session failover with OAuth2. This requires very specific openidConnectClient configuration that is not enabled by default:

uniqueUserIdentifier=”sub”
userIdentifier=”sub”
userIdentityToCreateSubject=”sub”
accessTokenInLtpaCookie=”true”
inboundPropagation=”supported”
audiences=”${clientId}”
validationEndpointUrl=”${userInfoEndpoint}”
validationMethod=”userinfo”
userInfoEndpointEnabled=”true”
userInfoEndpointUrl=”${userInfoEndpoint}”

The above configuration achieves two goals: it inserts the OAuth2 access_token  into Liberty’s LTPA cookie on LibertyNodeA , while also using the access_token  to validate and reconstruct a new LTPA cookie on LibertyNodeB  during session failover. When this occurs, the end-user’s request and authenticated session are seamlessly rolled over to the new node – and wouldn’t otherwise be aware except for the destruction and creation of a new LTPA cookie under the covers.

You should be aware of the following caveats:

  • Both Liberty nodes should be configured to accept the same audiences  (OAuth2 clientIds).
  • A node could fail at any time for any request. That means all requests should qualify for an openidConnectClient authFilter  that is appropriate for your configuration.
  • The provider-issued access_token  must contain the accessTokenInLtpaCookie increases the size of the cookie header substantially and your upstream infrastructure, such as Ingress, may need to be reconfigured as such.

Failure to adhere to the above will result in an unsuccessful failover, resulting in the user’s session being terminated with a corresponding message in the server’s console.log.

Error page configuration

It’s worth highlighting that other unforeseen authentication flow errors can occur, even with ideal configuration. For example, the connection between Liberty and the authentication provider could time out during OAuth2 token exchange. In that case, the application will fall back to Liberty’s default 401 unauthorized response. These responses are not user-friendly and typically do not offer a retry or redirect mechanism.

To counter this, as an industry best practice, it is your application’s responsibility to define generic or specific error pages as seen in this example.

A simple first-step recommendation would be to define a generic, “Sorry, something went wrong” page—regardless of the error. This would contain a link back to your application’s home page to restart the authentication process if an authenticated session has not yet been established. This would handle any errors during the final stages of the OAuth2 authentication flow.

Unfortunately, any errors emitted directly from the oidcclient webapp currently cannot be customised; Liberty doesn’t support this at time of writing.

Supplying configuration that limits errors that end-users might encounter

Through an informed understanding of the OAuth2 authentication flow on Liberty, we can supply configuration that limits errors that end-users might encounter. It is important to ensure that the vast majority of use-case scenarios are covered and thoroughly tested in your topology.

In particular, long idle time on login pages and the use of browser navigation buttons (refresh/back) are common use cases but often result in a high frequency of errors in default configurations. Enhanced configuration, together with custom error responses, will deliver a better login experience for all end-users.

Resources

 
Author
Dominic Panarello Software engineer specializing