[[_installed_adapter]]
==== CLI / Desktop Applications

{project_name} supports securing desktop 
(e.g. Swing, JavaFX) or CLI applications via the 
`KeycloakInstalled` adapter by performing the authentication step via the system browser.

The `KeycloakInstalled` adapter supports a `desktop` and a `manual`
variant. The desktop variant uses the system browser
to gather the user credentials. The manual variant
reads the user credentials from `STDIN`.

Tip: Google provides some more information about this approach on at 
https://developers.google.com/identity/protocols/OAuth2InstalledApp[OAuth2InstalledApp].

===== How it works

To authenticate a user with the `desktop` variant the `KeycloakInstalled` 
adapter opens a desktop browser window where a user uses the regular {project_name} 
login pages to login when the `loginDesktop()` method is called on the `KeycloakInstalled` object.

The login page URL is opened with redirect parameter 
that points to a local `ServerSocket` listening on a free ephemeral port 
on `localhost` which is started by the adapter.

After a succesful login the `KeycloakInstalled` receives the authorization code
from the incoming HTTP request and performs the authorization code flow. 
Once the code to token exchange is completed the `ServerSocket` is shutdown.

TIP: If the user already has an active {project_name} session then
the login form is not shown but the code to token exchange is continued,
which enables a smooth Web based SSO experience.

The client eventually receives the tokens (access_token, refresh_token, 
id_token) which can then be used to call backend services.

The `KeycloakInstalled` adapter provides support for renewal of stale tokens.

[[_installed_adapter_installation]]
===== Adapter Installation

[source,xml,subs="attributes+"]
----


<dependency>
	<groupId>org.keycloak</groupId>
	<artifactId>keycloak-installed-adapter</artifactId>
	<version>{project_versionMvn}</version>
</dependency>

----


===== Client Configuration

The application needs to be configured as a `public` OpenID Connect client with
`Standard Flow Enabled` and pass:[http://localhost:*] as an allowed `Valid Redirect URI`.

===== Usage

The `KeycloakInstalled` adapter reads it's configuration from
`META-INF/keycloak.json` on the classpath. Custom configurations
can be supplied with an `InputStream` or a `KeycloakDeployment`
through the `KeycloakInstalled` constructor.

In the example below, the client configuration for `desktop-app`
uses the following `keycloak.json`:

[source,json]
----

{
  "realm": "desktop-app-auth",
  "auth-server-url": "http://localhost:8081/auth",
  "ssl-required": "external",
  "resource": "desktop-app",
  "public-client": true,
  "use-resource-role-mappings": true
}

----

the following sketch demonstrates working with the `KeycloakInstalled` adapter:
[source,java]
----

// reads the configuration from classpath: META-INF/keycloak.json
KeycloakInstalled keycloak = new KeycloakInstalled();

// opens desktop browser
keycloak.loginDesktop();

AccessToken token = keycloak.getToken();
// use token to send backend request

// ensure token is valid for at least 30 seconds
long minValidity = 30L;
String tokenString = keycloak.getTokenString(minValidity, TimeUnit.SECONDS);


 // when you want to logout the user.
keycloak.logout();

----

TIP: The `KeycloakInstalled` class supports customization of the http responses returned by
login / logout requests via the `loginResponseWriter` and `logoutResponseWriter` attributes.

===== Example

The following provides an example for the configuration mentioned above.

[source,java]
----
import java.util.Locale;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.keycloak.adapters.installed.KeycloakInstalled;
import org.keycloak.representations.AccessToken;

public class DesktopApp {

	public static void main(String[] args) throws Exception {

		KeycloakInstalled keycloak = new KeycloakInstalled();
		keycloak.setLocale(Locale.ENGLISH);
		keycloak.loginDesktop();

		AccessToken token = keycloak.getToken();
		Executors.newSingleThreadExecutor().submit(() -> {

			System.out.println("Logged in...");
			System.out.println("Token: " + token.getSubject());
			System.out.println("Username: " + token.getPreferredUsername());
			try {
				System.out.println("AccessToken: " + keycloak.getTokenString());
			} catch (Exception ex) {
				ex.printStackTrace();
			}

			int timeoutSeconds = 20;
			System.out.printf("Logging out in...%d Seconds%n", timeoutSeconds);
			try {
				TimeUnit.SECONDS.sleep(timeoutSeconds);
			} catch (Exception e) {
				e.printStackTrace();
			}

			try {
				keycloak.logout();
			} catch (Exception e) {
				e.printStackTrace();
			}

			System.out.println("Exiting...");
			System.exit(0);
		});
	}
}
----