Problem statement
The Qpid JMS clients for AMQP 1.0 and AMQP 0-x support XOAUTH2 SASL mechanism to authenticate messaging connection against OAUTH2 based services. The XOAUTH2 SASL mechanism is described on Google Developers page OAuth 2.0 mechanism and involves sending a bearer token as part of initial client response as illustrated below:
base64("user=" {User} "^Aauth=Bearer " {Access Token} "^A^A")
The broker is supposed to validate the token and accept connections from the clients providing valid token. Depending from OAUTH2 authentication provider the broker can perform some extra client validation by invoking OAUTH2 authentication provider specific REST API.
The XOAUTH2 SASL mechanism works perfectly for an initial connection establishment. However, the client implementations of XOAUTH2 SASL mechanism do not have a mechanism to update an expired access token. Thus, when failover is used and connectivity is lost after token being expired, the restoration of connectivity is not possible, as the same expired token would be used in every re-connection attempt. As result, the existing failover mechanism will not be able to re-establish connectivity with XOAUTH2 SASL. The only way the client application can work around this issue is by tracking the token expiration timeout, closing an existing connection after token expiration and re-opening a new connection with a new token. Thus, using XOAUTH2 SASL authentication in production environments requires users to write a complex logic around tracking of expiration time and re-opening connections which can interrupt a messaging flow and might cause disruption in underlying business use cases.
Possible solutions
User controlled credential update
In JMS API the password based credentials can be passed either via connection URL or directly into API calls: ConnectionFactory#createConnection(String userName, String password), ConnectionFactory#createContext(String userName, String password). When credentials are set there is no way in JMS API to update the credentials. We need an extension to JMS API which would allow to pass into JMS Connection special objects capable of supplying credentials. Such objects can be used for an initial connection establishment and restoring the connectivity during failover.
A possible example of JMS connection extension
ConnectionFactory connectionFactory =... CredentialSupplier passwordCredentialSupplier = new PasswordCredentialSupplier(password); CredentialSupplier userNameCredentialSupplier = new UserNameCredentialSupplier(userName); Connection connection = connectionFactory.createConnection(userNameCredentialSupplier , passwordCredentialSupplier )
The API methods might look like below
ConnectionFactory#createConnection(CredentialSupplier... credentialSuppliers) ConnectionFactory#createContext(CredentialSupplier... credentialSuppliers)
The end-user can implement a custom CredentialSupplier to supply credentials on runtime when connection is being established or re-established. It would be up to CredentialSupplier implementation to check the current credentials and renew them when they expire.
The SASL mechanism implementations would need to be changed to operate with CredentialSupplier interface and its derivatives (UserNameCredentialSupplier, PasswordCredentialSupplier) to fetch the required credentials. The new interfaces and implementations extending CredentialSupplier can be added for new SASL mechanisms if required.
Authentication Failure Observer
A special Listener can be introduced to call on authentication failures. Thus, when authentication fails due to an expired credentials the messaging application can be notified through this Listener, which can be set on JMS Connection or Context. The credentials set methods (setPassword(), setUserName) can be added to JMS Connection or Context implementations. That should allow to update the credentials if required.
The following class diagram illustrates the idea