Fork me on GitHub

Clients: (v4.0)

A Client represents an authentication mechanism. It performs the login process and returns (if successful) a user profile. Many clients are available for the:

While most clients are self-sufficient, the HTTP clients require defining an Authenticator to handle the credentials validation.

Clients (like Authorizers) are generally defined in a security configuration.

Each client has a name which is by default the class name (like FacebookClient), but it can be explicitly set to another value with the setName method.

Understand the main features:


1) Direct vs indirect clients

Clients are of two kinds: direct for web services authentication and indirect for UI authentication. Here are their behaviors and differences:

  Direct clients = web services authentication Indirect clients = UI authentication
Authentication flows 1) Credentials are passed for each HTTP request (to the “security filter”) 1) The originally requested URL is saved in session (by the “security filter”)
2) The user is redirected to the identity provider (by the “security filter”)
3) Authentication happens at the identity provider (or locally for the FormClient and the IndirectBasicAuthClient)
4) The user is redirected back to the callback endpoint/URL (“callback filter”)
5) The user is redirected to the originally requested URL (by the “callback filter”)
How many times the login process occurs? The authentication happens for every HTTP request (in the “security filter”) via the defined Authenticator and ProfileCreator.
For performance reasons, a cache may be used by wrapping the current Authenticator in a LocalCachingAuthenticator or the user profile can be saved (by the Authenticator or ProfileCreator) into the web session using the available web context and the ProfileManager class
The authentication happens only once (in the “callback filter”)
Where is the user profile saved by default? In the HTTP request (stateless) In the web session (stateful)
Where are the credentials? Passed for every HTTP request (processed by the “security filter”) On the callback endpoint returned by the identity provider (and retrieved by the “callback filter”)
Are the credentials mandatory? Generally, no. If no credentials are provided, the direct client will be ignored (by the “security filter”) Generally, yes. Credentials are expected on the callback endpoint
What are the protected URLs? The URLs of the web service are protected by the “security filter” The URLs of the web application are protected by the “security filter”, but the callback URL is not protected as it is used during the login process when the user is still anonymous

2) Compute roles and permissions

To compute the appropriate roles and permissions of the authenticated user profile, you need to define an AuthorizationGenerator and attach it to the client.

Example:

AuthorizationGenerator authGen = (ctx, profile) -> {
  String roles = profile.getAttribute("roles");
  for (String role: roles.split(",")) {
    profile.addRole(role);
  }
  return profile;
};
client.addAuthorizationGenerator(authGen);

And you can add as many authorization generators as you want using the addAuthorizationGenerator method or a list of authorization generators using the setAuthorizationGenerators method.

In fact, the AuthorizationGenerator component can be used to do more than just computing roles and permissions, like defining the remember-me nature of a profile based on a remember-me checkbox of a form (see: RememberMeAuthorizationGenerator).


3) The callback URL

For an indirect client, you must define the callback URL which will be used in the login process: after a successful login, the identity provider will redirect the user back to the application on the callback URL.

On this callback URL, the “callback endpoint” must be defined to finish the login process.

As the callback URL can be shared between multiple clients, the callback URL can hold the information of the client (to be able to distinguish between the different clients), as a query parameter or as a path parameter.

Example:

FacebookClient facebookClient = new FacebookClient(fbKey, fbSecret);
TwitterClient twitterClient = new TwitterClient(twKey, twSecret);
Config config = new Config("http://localhost:8080/callback", facebookClient, twitterClient);

In that case, the callback URL of the FacebookClient is http://localhost:8080/callback?client_name=FacebookClient and the callback URL of the TwitterClient is http://localhost:8080/callback?client_name=TwitterClient.

This is the callback URL you must define on the identity provider side.

This happens because the default CallbackUrlResolver of the clients is the QueryParameterCallbackUrlResolver.

You can change the client_name parameter using the setClientNameParameter method of the QueryParameterCallbackUrlResolver.

But you can also use the PathParameterCallbackUrlResolver, which adds the client name as a path parameter.

Example:

OidcConfiguration configuration = new OidcConfiguration();
configuration.setClientId("788339d7-1c44-4732-97c9-134cb201f01f");
configuration.setSecret("we/31zi+JYa7zOugO4TbSw0hzn+hv2wmENO9AS3T84s=");
configuration.setDiscoveryURI("https://login.microsoftonline.com/38c46e5a-21f0-46e5-940d-3ca06fd1a330/.well-known/openid-configuration");
AzureAdClient azureAdClient = new AzureAdClient(configuration);
client.setCallbackUrlResolver(new PathParameterCallbackUrlResolver());
Clients clients = new Clients("http://localhost:8080/callback", azureAdClient);
Config config = new Config(clients);

In that case, the callback URL will be http://localhost:8080/callback/AzureAdClient for the AzureAdClient.

You may even use the NoParameterCallbackUrlResolver which left the callback URL untouched. In that case, no parameter will be added to the callback URL and no client will be retrieved on the callback endpoint. You will be forced to define a “default client” at the CallbackLogic level.

Example:

defaultCallbackLogic.setClient("FacebookClient");

The CallbackUrlResolver relies on a UrlResolver to complement the URL according to the current web context. The UrlResolver can be retrieved via the getUrlResolver() method of the client.

You can use the DefaultUrlResolver and handle relative URLs by using: defaultUrlResolver.setCompleteRelativeUrl(true). Or provide your own UrlResolver using the setUrlResolver method.


4) Profile definition

Most clients rely on the Authenticator and ProfileCreator components to validate credentials and create the user profile.

At the end of the login process, the returned user profile is created by the (internal) Authenticator or ProfileCreator, which holds a profile definition.

This profile definition can be overridden using the setProfileDefinition method.


5) AJAX requests

For an indirect client, if the user tries to access a protected URL, he will be redirected to the identity provider for login.

Though, if the incoming HTTP request is an AJAX one, no redirection will be performed and a 401 error page will be returned.

The HTTP request is considered to be an AJAX one if the value of the X-Requested-With header is XMLHttpRequest or if the is_ajax_request parameter or header is true. This is the behaviour of the DefaultAjaxRequestResolver.

The DefaultAjaxRequestResolver will only compute the redirection URL and add it as a header if the addRedirectionUrlAsHeader property is set to true.

But you can provide your own AjaxRequestResolver with: client.setAjaxRequestResolver(myAjaxRequestResolver);.


6) The Client methods

The Client interface has the following methods:

Method Usage
Optional<RedirectionAction> getRedirectionAction(WebContext context) (only for indirect clients) It returns the redirection action to redirect the user to the identity provider for login.
The redirection of the user to the identity provider is defined via a RedirectionActionBuilder
Optional<C> getCredentials(WebContext context) It extracts the credentials from the HTTP request and validates them.
The extraction of the credentials are done by a CredentialsExtractor while the credentials validation is ensured by an Authenticator
Optional<UserProfile> getUserProfile(C credentials, WebContext context It builds the authenticated user profile.
The creation of the authenticated user profile is performed by a ProfileCreator
Optional<UserProfile> renewUserProfile(UserProfile profile, WebContext context) It returns the renewed user profile
Optional<RedirectionAction> getLogoutAction(WebContext context, UserProfile currentProfile, String targetUrl) It returns the redirect action to call the identity provider logout.
The logout redirect action computation is done by a LogoutActionBuilder

Clients are generally populated with default sub-components: RedirectionActionBuilder, CredentialsExtractor, ProfileCreator, LogoutActionBuilder and Authenticator, except for HTTP clients where the Authenticator must be defined. Sub-components can of course be changed for various customizations.


7) Originally requested URLs

An originally requested URL is the URL called before the authenticated process starts: it is restored from the callback URL after the login process has been completed.

It is handled in the DefaultSecurityLogic and in the CallbackSecurityLogic by the SavedRequestHandler component. By default, it’s a DefaultSavedRequestHandler which handles GET and POST requests.


8) Silent login

When using an IndirectClient, the login process can fail or be cancelled at the external identity provider level.

Thus, no user profile is created and the access is not be granted to the secured resources (401 error).

Though, you may still want to access the web resources if the login process has failed or been cancelled.

For that, you can return a custom profile instead of no profile by using the setProfileFactoryWhenNotAuthenticated method of the client.

Example:

myClient.setProfileFactoryWhenNotAuthenticated(p -> AnonymousProfile.INSTANCE);
In that case, the access is granted to all secured resources for the whole web session unless the proper Authorizers have been defined.