[[_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+"] ---- org.keycloak keycloak-installed-adapter {project_versionMvn} ---- ===== 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); }); } } ----