Login to the Core API (Java)

This tutorial will give you a brief overview of the most important login procedures for the yuuvis® API system and how to use them in a Java Client application

Table of Contents

Introduction

To interact with the yuuvis® API, it is necessary to log in with a user account. The user accounts are managed by at least one external identity provider (such as Keycloak or the Active Directory Federation Service). It is possible to configure different identity providers for different tenants. Each user account belongs to exactly one tenant, which must be specified with each login.

The full implementation of this example can be found in this git repository.

Check out our graphical overview of the architecture which describes the basic use case flow for logging in to the core API.

Requirements

To work through this tutorial, the following is required:

  • Set-up yuuvis® API system (see Installation Guide)
  • A user with at least read permissions on a document type in the system (see tutorial for permissions)
  • Simple Maven project
  • Preconfigured yuuvis® API system with Keycloak as the identity provider
    • Keycloak contains a realm default
    • In the realm default, the Authentication service is registered as a client under the name enaio
    • The realm default contains a user clouduser with the password secret

Session Management

The login is done using request headers, which are passed during the first call of an API function. The calling HTTP client must have a Cookie Manager to enrich further requests with the session cookie (GWSESSIONID). This prevents the user from logging on again for each request. In the tutorial, the OkHttpClient by Square, Inc. is used to this. Therefore, the following block must be added to the Maven dependencies in the pom.xml of the project:

pom.xml
<dependency>
	<groupId>com.squareup.okhttp3</groupId>
	<artifactId>okhttp</artifactId>
	<version>3.12.0</version>
</dependency>

<dependency>
	<groupId>com.squareup.okhttp3</groupId>
	<artifactId>okhttp-urlconnection</artifactId>
	<version>3.12.0</version>
</dependency>

To activate Cookie Management in the OkHttpClient, you can do the following:

Activate Cookie Management in OkHttpClient
CookieJar cookieJar = new JavaNetCookieJar(new CookieManager(null, CookiePolicy.ACCEPT_ALL));
OkHttpClient client = new OkHttpClient.Builder().cookieJar(cookieJar).build();


Secure Data Transmission with SSL

To use encrypted (HTTPS) endpoints with an OkHttpClient, it must be configured for the SSL encryption protocol. For this, it must also be capable of certificate treatment according to the X-509 standard. For this, the OkHttpClient receives a hostnameVerifier and a sslSocketFactory at initialization to check incoming certificates with the TLS handshake. In our example, we just keep building our X509TrustManager for the sslSocketFactory for demonstration purposes, so that certificates are almost always accepted. The following code creates an OkHttpClient that is both session- and SSL-enabled.

OkHttpClient with Session Handling and SSL
private OkHttpClient client = null;

// necessary to obtain access tokens via SSL
X509TrustManager trustManager = new X509TrustManager()
{
   public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {}
   public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {}
   public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
};


CookieJar cookieJar = new JavaNetCookieJar(new CookieManager(null, CookiePolicy.ACCEPT_ALL));

SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, new TrustManager[]{this.trustManager}, new SecureRandom());

// create HTTP Client
this.client = new OkHttpClient.Builder()
    .cookieJar(cookieJar)
    .hostnameVerifier((s, sslSession) -> true)
    .sslSocketFactory(sslContext.getSocketFactory(), this.trustManager)
    .build();

Required System Data

To log in to the Core API, a client needs some information about the system. Of course, they first need valid credentials (username / password) of a user existing in the system. In addition, the Keycloak login client must be able to identify itself as an approved application, so it must know a registered clientId with an associated clientSecret. In addition, both pairs of credentials are only valid for a certain tenant whose name must also be known to the client for the requests. Finally, the client must also know the one accessible base URL of the Core API and the associated Keycloak identity provider.

Variables for the Login
private String userName = "clouduser";
private String userPwd = "secret";
private String userTenant = "default";
private String clientId = "clouduser";
private String clientSecret = "cd7e6ce4-781b-40db-af5f-e4106926e96c";  //client secret, obtainable through keycloak credentials tab of client matching client id and tenant
private String yuuvisBaseUrl= "http://10.10.6.242";             //Base URL of yuuvisclient
private String keycloakBaseUrl = "https://10.10.6.252:8443";   //Base URL of keycloak identity provider

Generating a Secret

To log in with an oAuth2 access token, the client must authenticate itself to Keycloak with its client secret. This secret is static and can be taken from the Keycloak interface. To do this, log on to the Keycloak interface with a technical user, select the tenant applicable to the client, select the correct client under "Clients" (here "Enaio") and take out the secret of the client under the "Credentials" tab.

Login Procedures

Login with User Name and Password

The user name, password, and associated tenant are passed as HTTP headers. The user name and password must be encoded with Base64 in the same way as for classic Basic authentication. The headers are then sent to any Core API endpoint.

Login with User Name and Password
byte[] credentials = "clouduser:secret".getBytes(StandardCharsets.UTF_8);
String authorization = "Basic " + Base64.getEncoder().encodeToString(credentials);

Headers headers = new Headers.Builder()
	.add("Authorization", authorization)
	.add("X-ID-TENANT-NAME", "default")
	.build();

Login with an OAuth 2.0 Access Token

For this login procedure, the application must have received an access token from the identity provider from a third-party application (for example, a proxy). The access token and the associated tenant are passed as HTTP headers for login.

Keycloak can request an access token with the Password Credentials Grant Flow. The request must contain the user name, the user password, the client ID and the secret as parameters. Keycloak's response contains a JSON object from which the access token and token type must be extracted. In the example, JsonPath is used for this.

Login with an OAuth 2.0 Access Token
String payload = "client_id=enaio&" +
                 "client_secret=4c5254363c1d&" +
                 "username=clouduser&" +
                 "password=secret&" +
                 "grant_type=password";

//retrieve access token from identity provider (Keycloak)
Request.Builder request = new Request.Builder()
	.url(keycloakBaseUrl+"/auth/realms/default/protocol/openid-connect/token")
	.post(RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), payload))
	.build();

String responseJson = this.client.newCall(request).execute().body().string();

DocumentContext context = JsonPath.parse(responseJson);
String tokenType = context.read("token_type");
String accessToken = context.read("access_token");

Finally, the logon headers are created and sent to any Core API endpoint.

Creating Headers
Headers headers = new Headers.Builder()
	.add("Authorization", tokenType + " " + accessToken)
	.add("X-ID-TENANT-NAME", "default")
	.build();

Login with the OAuth 2.0 Device Flow

With this logon procedure, the client application calls the system browser so that the user can log on to an HTML form. The OAuth 2.0 Device Flow is used for this purpose. The procedure is done in the following four steps:

  1. Start the login process and read out the parameters Device CodeUser Code, and Verification URI. The example uses JsonPath for this. The parameter values lose their validity after five minutes. In this time window, the user must have finished logging in to the browser.

    OAuth 2.0 Device Flow: Step 1
    Request.Builder startRequest = new Request.Builder().url(keycloakBaseUrl+"/tenant/default/loginDevice");
    
    String responseJson = this.client.newCall(startRequest.build()).execute().body().string();
    DocumentContext context = JsonPath.parse(responseJson);
    
    String deviceCode = context.read("device_code");
    String userCode = context.read("user_code");
    String verificationUri = context.read("verification_uri");
  2. The Verification URI is opened with the User Code as a parameter in the system browser. The Authentication service then forwards to the login page of the identity provider, where the user can log in.

    OAuth 2.0 Device Flow: Step 2
    Desktop.getDesktop().browse(new URI(keycloakBaseUrl + verificationUri + "?user_code=" + userCode));
  3. At regular intervals, the client application queries the Authentication service to see whether the user has successfully logged in. This is the case if the status request receives the return code OK (200). The response of the status request contains a JSON object from which the access token and the token type must be extracted. In the example, JsonPath is used for this.

    OAuth 2.0 Device Flow: Step 3
    String tokenType = null;
    String accessToken = null;
    
    for (int i = 1 ; i != 30 ; i++, Thread.sleep(2000))
    {
    	Request.Builder pollingRequest = new Request.Builder().url(keycloakBaseUrl+"/auth/info/state?device_code=" + deviceCode);
    	Response pollingResponse = this.client.newCall(pollingRequest.build()).execute();
    
    	if (pollingResponse.code() != 200) continue;
    
    	context = JsonPath.parse(pollingResponse.body().string());
    	tokenType = context.read("token_type");
    	accessToken = context.read("access_token");
    
    	break;
    }
  4. Finally, the logon headers are assembled and sent to any Core API endpoint.

    OAuth 2.0 Device Flow: Step 4
    Headers headers = new Headers.Builder()
    	.add("Authorization", tokenType + " " + accessToken)
    	.add("X-ID-TENANT-NAME", "default")
    	.build();

Logout

After all necessary API operations have been performed, the session established by the logon can be closed.

Log Out of the Core API
Request.Builder logoutRequest = new Request.Builder().url(keycloakBaseUrl+"/logout");
this.client.newCall(logoutRequest.build()).execute();

Summary

This tutorial explains how an OkHttp3 Java client implements the different logon procedures for the Core API.

The complete code example can be found in this git repository.


More Tutorials

Authentication against the Core API

This article details the authentication methods compatible with the yuuvis® Core API and how to choose the correct one for your use case. Keep reading

Importing Documents

This tutorial shows how documents can be imported into a yuuvis® API system via the Core API. During this tutorial, a short Java application will be developed that implements the HTTP requests for importing documents. We additionally provide a JavaScript version of this tutorial. Keep reading

Retrieving Documents

In this tutorial, we will discuss various ways to retrieve objects via the Core API from the yuuvis® API system using an OkHttp3 Java client. Keep reading