Okta IDX Java SDK :: Impl

The Okta IDX Java SDK core implementation .jar is used at runtime to support API invocations. This implementation jar should be a runtime dependency only and should NOT be depended on at compile time by your code. The implementations within this jar can change at any time without warning - use it with runtime scope only.

License

License

Categories

Categories

Java Languages
GroupId

GroupId

com.okta.idx.sdk
ArtifactId

ArtifactId

okta-idx-java-impl
Last Version

Last Version

0.1.0-beta.5
Release Date

Release Date

Type

Type

jar
Description

Description

Okta IDX Java SDK :: Impl
The Okta IDX Java SDK core implementation .jar is used at runtime to support API invocations. This implementation jar should be a runtime dependency only and should NOT be depended on at compile time by your code. The implementations within this jar can change at any time without warning - use it with runtime scope only.
Project Organization

Project Organization

Okta

Download okta-idx-java-impl

How to add to project

<!-- https://jarcasting.com/artifacts/com.okta.idx.sdk/okta-idx-java-impl/ -->
<dependency>
    <groupId>com.okta.idx.sdk</groupId>
    <artifactId>okta-idx-java-impl</artifactId>
    <version>0.1.0-beta.5</version>
</dependency>
// https://jarcasting.com/artifacts/com.okta.idx.sdk/okta-idx-java-impl/
implementation 'com.okta.idx.sdk:okta-idx-java-impl:0.1.0-beta.5'
// https://jarcasting.com/artifacts/com.okta.idx.sdk/okta-idx-java-impl/
implementation ("com.okta.idx.sdk:okta-idx-java-impl:0.1.0-beta.5")
'com.okta.idx.sdk:okta-idx-java-impl:jar:0.1.0-beta.5'
<dependency org="com.okta.idx.sdk" name="okta-idx-java-impl" rev="0.1.0-beta.5">
  <artifact name="okta-idx-java-impl" type="jar" />
</dependency>
@Grapes(
@Grab(group='com.okta.idx.sdk', module='okta-idx-java-impl', version='0.1.0-beta.5')
)
libraryDependencies += "com.okta.idx.sdk" % "okta-idx-java-impl" % "0.1.0-beta.5"
[com.okta.idx.sdk/okta-idx-java-impl "0.1.0-beta.5"]

Dependencies

compile (6)

Group / Artifact Type Version
com.okta.idx.sdk : okta-idx-java-api jar 0.1.0-beta.5
com.okta.commons : okta-config-check jar 1.2.5
com.okta.commons : okta-http-api jar 1.2.5
org.yaml : snakeyaml jar 1.27
org.slf4j : slf4j-api jar 1.7.29
javax.annotation : javax.annotation-api Optional jar 1.3.2

runtime (1)

Group / Artifact Type Version
com.okta.commons : okta-http-okhttp jar 1.2.5

test (7)

Group / Artifact Type Version
org.mockito : mockito-core jar 3.1.0
org.testng : testng jar 7.0.0
org.slf4j : slf4j-simple jar 1.7.29
org.hamcrest : hamcrest jar 2.2
org.codehaus.groovy : groovy jar 2.5.8
org.codehaus.groovy : groovy-templates jar 2.5.8
org.codehaus.groovy : groovy-json jar 2.5.8

Project Modules

There are no modules declared in this project.

Maven Central License Support API Reference Build Status

Okta IDX Java SDK

This repository contains the Okta IDX SDK for Java. This SDK can be used in your server-side code to assist in authenticating users against the Okta Identity Engine.

The use of this SDK requires usage of the Okta Identity Engine. This functionality is in general availability but is being gradually rolled out to customers. If you want to request to gain access to the Okta Identity Engine, please reach out to your account manager. If you do not have an account manager, please reach out to [email protected] for more information.

⚠️ Beta alert! This library is in beta. See release status for more information.

Release status

This library uses semantic versioning and follows Okta's Library Version Policy.

Version Status
0.1.0 ⚠️ Beta

The latest release can always be found on the releases page.

Need help?

If you run into problems using the SDK, you can

Getting started

Prerequisites

  • JDK 8 or later

To use this SDK, you will need to include the following dependencies:

For Apache Maven:

<dependency>
    <groupId>com.okta.idx.sdk</groupId>
    <artifactId>okta-idx-java-api</artifactId>
    <version>${okta.sdk.version}</version>
</dependency>
<dependency>
    <groupId>com.okta.idx.sdk</groupId>
    <artifactId>okta-idx-java-impl</artifactId>
    <version>${okta.sdk.version}</version>
    <scope>runtime</scope>
</dependency>

For Gradle:

compile "com.okta.idx.sdk:okta-idx-java-api:${okta.sdk.version}"
runtime "com.okta.idx.sdk:okta-idx-java-impl:${okta.sdk.version}"

where okta.sdk.version is the latest stable release version listed here.

SNAPSHOT Dependencies

Snapshots are deployed off of the 'master' branch to OSSRH and can be consumed using the following repository configured for Apache Maven or Gradle:

https://oss.sonatype.org/content/repositories/snapshots/

You will also need:

Usage guide

The below code snippets will help you understand how to use this library. Alternatively, you can look at Quickstart to help get started.

Once you initialize a Client, you can call methods to make requests to the Okta API.

Create the Client

IDXClient client = Clients.builder()
        .setIssuer("https://{yourOktaDomain}/oauth2/{authorizationServerId}") // e.g. https://foo.okta.com/oauth2/default, https://foo.okta.com/oauth2/ausar5vgt5TSDsfcJ0h7
        .setClientId("{clientId}")
        .setClientSecret("{clientSecret}")
        .setScopes(new HashSet<>(Arrays.asList("openid", "email")))
        .setRedirectUri("{redirectUri}") // must match the redirect uri in client app settings/console
        .build();

Get State Handle

IDXClientContext idxClientContext = client.interact();
IDXResponse idxResponse = client.introspect(idxClientContext);
String stateHandle = idxResponse.getStateHandle();

Get Interaction Handle and Code Verifier

IDXClientContext idxClientContext = client.interact();
String interactionHandle = idxClientContext.getInteractionHandle();
String codeVerifier = idxClientContext.getCodeVerifier();

Get New tokens (access_token/id_token/refresh_token)

In this example the sign-on policy has no authenticators required.

Note: Steps to identify the user might change based on the Org configuration.

// build client
IDXClient client = Clients.builder()
        .setIssuer("https://{yourOktaDomain}/oauth2/{authorizationServerId}") // e.g. https://foo.okta.com/oauth2/default, https://foo.okta.com/oauth2/ausar5vgt5TSDsfcJ0h7
        .setClientId("{clientId}")
        .setClientSecret("{clientSecret}")
        .setScopes(new HashSet<>(Arrays.asList("openid", "profile", "offline_access")))
        .setRedirectUri("{redirectUri}") // must match the redirect uri in client app settings/console
        .build();

// get client context
IDXClientContext idxClientContext = client.interact();

// introspect
IDXResponse idxResponse = client.introspect(idxClientContext);
String stateHandle = idxResponse.getStateHandle();

// check remediation options to continue the flow
RemediationOption[] remediationOptions = idxResponse.remediation().remediationOptions();
Optional<RemediationOption> remediationOptionsOptional = Arrays.stream(remediationOptions)
        .findFirst();
RemediationOption remediationOption = remediationOptionsOptional.get();
FormValue[] formValues = remediationOption.form();
IdentifyRequest identifyRequest = IdentifyRequestBuilder.builder()
        .withIdentifier("{identifier}") // email
        .withStateHandle(stateHandle)
        .build();

// identify
idxResponse = remediationOption.proceed(client, identifyRequest);

// get remediation options to go to the next step
remediationOptions = idxResponse.remediation().remediationOptions();
remediationOptionsOptional = Arrays.stream(remediationOptions)
        .filter(x -> "select-authenticator-authenticate".equals(x.getName()))
        .findFirst();
remediationOption = remediationOptionsOptional.get();

// get authenticator options
Map<String, String> authenticatorOptions = remediationOption.getAuthenticatorOptions();
log.info("Authenticator Options: {}", authenticatorOptions);

// select password authenticator
Authenticator passwordAuthenticator = new Authenticator();
passwordAuthenticator.setId(authenticatorOptions.get("password"));
passwordAuthenticator.setMethodType("password");

// build password authenticator challenge request
ChallengeRequest passwordAuthenticatorChallengeRequest = ChallengeRequestBuilder.builder()
        .withAuthenticator(passwordAuthenticator)
        .withStateHandle(stateHandle)
        .build();
idxResponse = remediationOption.proceed(client, passwordAuthenticatorChallengeRequest);

// check remediation options to continue the flow
remediationOptions = idxResponse.remediation().remediationOptions();
remediationOptionsOptional = Arrays.stream(remediationOptions)
        .filter(x -> "challenge-authenticator".equals(x.getName()))
        .findFirst();
remediationOption = remediationOptionsOptional.get();

// answer password authenticator challenge
Credentials credentials = new Credentials();
credentials.setPasscode("password".toCharArray());

// build answer password authenticator challenge request
AnswerChallengeRequest passwordAuthenticatorAnswerChallengeRequest = AnswerChallengeRequestBuilder.builder()
        .withStateHandle(stateHandle)
        .withCredentials(credentials)
        .build();
idxResponse = remediationOption.proceed(client, passwordAuthenticatorAnswerChallengeRequest);

// exchange interaction code for token
TokenResponse tokenResponse = idxResponse.getSuccessWithInteractionCode().exchangeCode(client, idxClientContext);
log.info("Exchanged interaction code for token: \naccessToken: {}, \nidToken: {}, \nrefreshToken: {}, \ntokenType: {}, \nscope: {}, \nexpiresIn:{}",
        tokenResponse.getAccessToken(),
        tokenResponse.getIdToken(),
        tokenResponse.getRefreshToken(),
        tokenResponse.getTokenType(),
        tokenResponse.getScope(),
        tokenResponse.getExpiresIn());

Cancel the OIE transaction and start new after that

In this example the Org is configured to require email as a second authenticator. After answering password challenge, a cancel request is send right before answering the email challenge.

// build client
IDXClient client = Clients.builder()
        .setIssuer("https://{yourOktaDomain}/oauth2/{authorizationServerId}") // e.g. https://foo.okta.com/oauth2/default, https://foo.okta.com/oauth2/ausar5vgt5TSDsfcJ0h7
        .setClientId("{clientId}")
        .setClientSecret("{clientSecret}")
        .setScopes(new HashSet<>(Arrays.asList("openid", "profile", "offline_access")))
        .setRedirectUri("{redirectUri}") // must match the redirect uri in client app settings/console
        .build();

// get client context
IDXClientContext idxClientContext = client.interact();

// exchange interactHandle for stateHandle
IDXResponse idxResponse = client.introspect(idxClientContext);
String stateHandle = idxResponse.getStateHandle();

// check remediation options to continue the flow
RemediationOption[] remediationOptions = idxResponse.remediation().remediationOptions();
Optional<RemediationOption> remediationOptionsOptional = Arrays.stream(remediationOptions)
        .findFirst();
RemediationOption remediationOption = remediationOptionsOptional.get();
FormValue[] formValues = remediationOption.form();
IdentifyRequest identifyRequest = IdentifyRequestBuilder.builder()
        .withIdentifier("{identifier}") // email
        .withStateHandle(stateHandle)
        .build();

// identify
idxResponse = remediationOption.proceed(client, identifyRequest);

// get remediation options to go to the next step
remediationOptions = idxResponse.remediation().remediationOptions();
remediationOptionsOptional = Arrays.stream(remediationOptions)
        .filter(x -> "select-authenticator-authenticate".equals(x.getName()))
        .findFirst();
remediationOption = remediationOptionsOptional.get();

// select password authenticator
Authenticator passwordAuthenticator = new Authenticator();

// authenticator's 'id' value from remediation option above
passwordAuthenticator.setId("{id}");

// authenticator's 'methodType' value from remediation option above
passwordAuthenticator.setMethodType("{methodType}");

// build password authenticator challenge request
ChallengeRequest passwordAuthenticatorChallengeRequest = ChallengeRequestBuilder.builder()
        .withAuthenticator(passwordAuthenticator)
        .withStateHandle(stateHandle)
        .build();
idxResponse = remediationOption.proceed(client, passwordAuthenticatorChallengeRequest);

// check remediation options to continue the flow
remediationOptions = idxResponse.remediation().remediationOptions();
remediationOptionsOptional = Arrays.stream(remediationOptions)
        .filter(x -> "challenge-authenticator".equals(x.getName()))
        .findFirst();
remediationOption = remediationOptionsOptional.get();

// answer password authenticator challenge
Credentials credentials = new Credentials();
credentials.setPasscode("{password}".toCharArray());

// build answer password authenticator challenge request
AnswerChallengeRequest passwordAuthenticatorAnswerChallengeRequest = AnswerChallengeRequestBuilder.builder()
        .withStateHandle(stateHandle)
        .withCredentials(credentials)
        .build();
idxResponse = remediationOption.proceed(client, passwordAuthenticatorAnswerChallengeRequest);

// cancel
idxResponse = client.cancel("{stateHandle}");

// cancel returns new state handle
String newStateHandle = idxResponse.getStateHandle();

// check remediation options to continue the flow for new transaction (with new state handle)
remediationOptions = idxResponse.remediation().remediationOptions();
remediationOptionsOptional = Arrays.stream(remediationOptions)
        .findFirst();
remediationOption = remediationOptionsOptional.get();

Remediation/MFA scenarios with sign-on policy

Login using password + enroll security question authenticator

In this example, the Org is configured to require a security question as a second authenticator. After answering the password challenge, users have to select security question and then select a question and enter an answer to finish the process.

Note: Steps to identify the user might change based on your Org configuration.

// get client context
IDXClientContext idxClientContext = client.interact();

// introspect
IDXResponse idxResponse = client.introspect(idxClientContext);
String stateHandle = idxResponse.getStateHandle();
Credentials credentials = new Credentials();
credentials.setPasscode("{password}".toCharArray());
IdentifyRequest identifyRequest = IdentifyRequestBuilder.builder()
        .withIdentifier("{identifier}") // email
        .withCredentials(credentials)
        .withStateHandle(stateHandle)
        .build();

// identify
idxResponse = remediationOption.proceed(client, identifyRequest);

// check remediation options to go to the next step
RemediationOption[] remediationOptions = idxResponse.remediation().remediationOptions();
Optional<RemediationOption> remediationOptionsSelectAuthenticatorOptional = Arrays.stream(remediationOptions)
        .filter(x -> "select-authenticator-enroll".equals(x.getName()))
        .findFirst();
RemediationOption remediationOptionsSelectAuthenticatorOption = remediationOptionsSelectAuthenticatorOptional.get();

// select an authenticator
Authenticator secQnEnrollmentAuthenticator = new Authenticator();

// authenticator's 'id' value from remediation option above
secQnEnrollmentAuthenticator.setId("{id}");

// authenticator's 'methodType' value from remediation option above
secQnEnrollmentAuthenticator.setMethodType("{methodType}");

// build enroll request
EnrollRequest enrollRequest = EnrollRequestBuilder.builder()
        .withAuthenticator(secQnEnrollmentAuthenticator)
        .withStateHandle("{stateHandle}")
        .build();

// proceed
idxResponse = remediationOptionsSelectAuthenticatorOption.proceed(client, enrollRequest);

// get remediation options to go to the next step
remediationOptions = idxResponse.remediation().remediationOptions();
Optional<RemediationOption> remediationOptionsEnrollAuthenticatorOptional = Arrays.stream(remediationOptions)
        .filter(x -> "enroll-authenticator".equals(x.getName()))
        .findFirst();
RemediationOption remediationOptionsEnrollAuthenticatorOption = remediationOptionsEnrollAuthenticatorOptional.get();
FormValue[] enrollAuthenticatorFormValues = remediationOptionsEnrollAuthenticatorOption.form();
Optional<FormValue> enrollAuthenticatorFormOptional = Arrays.stream(enrollAuthenticatorFormValues)
        .filter(x -> "credentials".equals(x.getName()))
        .findFirst();
FormValue enrollAuthenticatorForm = enrollAuthenticatorFormOptional.get();
Options[] enrollmentAuthenticatorOptions = enrollAuthenticatorForm.options();
Optional<Options> chooseSecQnOptionOptional = Arrays.stream(enrollmentAuthenticatorOptions)
        .filter(x -> "Choose a security question".equals(x.getLabel()))
        .findFirst();

// view default security questions list
Options choseSecQnOption = chooseSecQnOptionOptional.get();
Credentials secQnEnrollmentCredentials = new Credentials();

// e.g. "favorite_sports_player"
secQnEnrollmentCredentials.setQuestionKey("{questionKey}");

// e.g. "What is the name of your first stuffed animal?"
secQnEnrollmentCredentials.setQuestion("{question}");

// e.g. "Tiger Woods"
secQnEnrollmentCredentials.setAnswer("{answer}".toCharArray());
AnswerChallengeRequest answerChallengeRequest = AnswerChallengeRequestBuilder.builder()
        .withStateHandle("{stateHandle}")
        .withCredentials(secQnEnrollmentCredentials)
        .build();

// proceed
idxResponse = remediationOptionsEnrollAuthenticatorOption.proceed(client, answerChallengeRequest);

Login using password + email authenticator

In this example, the Org is configured to require an email as a second authenticator. After answering the password challenge, users have to select email and enter the code to finish the process.

Note: Steps to identify the user might change based on your Org configuration.

Note: If users click a magic link instead of providing a code, they will be redirected to the login page with a valid session if applicable.

// get client context
IDXClientContext idxClientContext = client.interact();

// exchange interactHandle for stateHandle
IDXResponse idxResponse = client.introspect(idxClientContext);
String stateHandle = idxResponse.getStateHandle();

// check remediation options to continue the flow
RemediationOption[] remediationOptions = idxResponse.remediation().remediationOptions();
Optional<RemediationOption> remediationOptionsOptional = Arrays.stream(remediationOptions)
        .findFirst();
RemediationOption remediationOption = remediationOptionsOptional.get();
FormValue[] formValues = remediationOption.form();
IdentifyRequest identifyRequest = IdentifyRequestBuilder.builder()
        .withIdentifier("{identifier}") // email
        .withStateHandle(stateHandle)
        .build();

// identify
idxResponse = remediationOption.proceed(client, identifyRequest);

// get remediation options to go to the next step
remediationOptions = idxResponse.remediation().remediationOptions();
remediationOptionsOptional = Arrays.stream(remediationOptions)
        .filter(x -> "select-authenticator-authenticate".equals(x.getName()))
        .findFirst();
remediationOption = remediationOptionsOptional.get();

// get authenticator options
Map<String, String> authenticatorOptions = remediationOption.getAuthenticatorOptions();
log.info("Authenticator Options: {}", authenticatorOptions);

// select password authenticator
Authenticator passwordAuthenticator = new Authenticator();
passwordAuthenticator.setId(authenticatorOptions.get("password"));
passwordAuthenticator.setMethodType("password");

// build password authenticator challenge request
ChallengeRequest passwordAuthenticatorChallengeRequest = ChallengeRequestBuilder.builder()
        .withAuthenticator(passwordAuthenticator)
        .withStateHandle(stateHandle)
        .build();
idxResponse = remediationOption.proceed(client, passwordAuthenticatorChallengeRequest);

// check remediation options to continue the flow
remediationOptions = idxResponse.remediation().remediationOptions();
remediationOptionsOptional = Arrays.stream(remediationOptions)
        .filter(x -> "challenge-authenticator".equals(x.getName()))
        .findFirst();
remediationOption = remediationOptionsOptional.get();

// answer password authenticator challenge
Credentials credentials = new Credentials();
credentials.setPasscode("{password}".toCharArray());

// build answer password authenticator challenge request
AnswerChallengeRequest passwordAuthenticatorAnswerChallengeRequest = AnswerChallengeRequestBuilder.builder()
        .withStateHandle(stateHandle)
        .withCredentials(credentials)
        .build();
idxResponse = remediationOption.proceed(client, passwordAuthenticatorAnswerChallengeRequest);

// check remediation options to continue the flow
remediationOptions = idxResponse.remediation().remediationOptions();
remediationOptionsOptional = Arrays.stream(remediationOptions)
        .filter(x -> "select-authenticator-authenticate".equals(x.getName()))
        .findFirst();
remediationOption = remediationOptionsOptional.get();

// get authenticator options
authenticatorOptions = remediationOption.getAuthenticatorOptions();
log.info("Authenticator Options: {}", authenticatorOptions);

// select email authenticator
Authenticator emailAuthenticator = new Authenticator();
emailAuthenticator.setId(authenticatorOptions.get("email"));
emailAuthenticator.setMethodType("email");

// build email authenticator challenge request
ChallengeRequest emailAuthenticatorChallengeRequest = ChallengeRequestBuilder.builder()
        .withAuthenticator(emailAuthenticator)
        .withStateHandle(stateHandle)
        .build();
idxResponse = remediationOption.proceed(client, emailAuthenticatorChallengeRequest);

// answer email authenticator challenge
remediationOptions = idxResponse.remediation().remediationOptions();
remediationOptionsOptional = Arrays.stream(remediationOptions)
        .filter(x -> "challenge-authenticator".equals(x.getName()))
        .findFirst();
remediationOption = remediationOptionsOptional.get();
credentials = new Credentials();

// passcode received in email
credentials.setPasscode("{passcode}".toCharArray());

// build answer email authenticator challenge request
AnswerChallengeRequest emailAuthenticatorAnswerChallengeRequest = AnswerChallengeRequestBuilder.builder()
        .withStateHandle(stateHandle)
        .withCredentials(credentials)
        .build();
idxResponse = remediationOption.proceed(client, emailAuthenticatorAnswerChallengeRequest);

// check if we landed success on login
if (idxResponse.isLoginSuccessful()) {
    log.info("Login Successful!");
    // exchange the received interaction code for a token
    TokenResponse tokenResponse = idxResponse.getSuccessWithInteractionCode().exchangeCode(client, idxClientContext);
    log.info("Exchanged interaction code for token: \naccessToken: {}, \nidToken: {}, \nrefreshToken: {}, \ntokenType: {}, \nscope: {}, \nexpiresIn:{}",
            tokenResponse.getAccessToken(),
            tokenResponse.getIdToken(),
            tokenResponse.getRefreshToken(),
            tokenResponse.getTokenType(),
            tokenResponse.getScope(),
            tokenResponse.getExpiresIn());
}

Login using password + phone authenticator (SMS/Voice)

In this example, the Org is configured to require a Phone factor (SMS/Voice) as a second authenticator. After answering the password challenge, users have to select SMS/Voice and enter the code to finish the process.

Note: Steps to identify the user might change based on your Org configuration.

// get client context
IDXClientContext idxClientContext = client.interact();

// exchange interactHandle for stateHandle
IDXResponse idxResponse = client.introspect(idxClientContext);
String stateHandle = idxResponse.getStateHandle();
IdentifyRequest identifyRequest = IdentifyRequestBuilder.builder()
        .withIdentifier("{identifier}") // email
        .withStateHandle(stateHandle)
        .build();

// identify
idxResponse = remediationOption.proceed(client, identifyRequest);

// get remediation options to go to the next step
RemediationOption[] remediationOptions = idxResponse.remediation().remediationOptions();
Optional<RemediationOption> remediationOptionsOptional = Arrays.stream(remediationOptions)
        .filter(x -> "select-authenticator-authenticate".equals(x.getName()))
        .findFirst();
remediationOption = remediationOptionsOptional.get();

// get authenticator options
Map<String, String> authenticatorOptions = remediationOption.getAuthenticatorOptions();
log.info("Authenticator Options: {}", authenticatorOptions);

// select phone authenticator (sms or voice)
Authenticator phoneAuthenticator = new Authenticator();
phoneAuthenticator.setId(authenticatorOptions.get("sms,voice"));

/* id is the same for both sms and voice */
phoneAuthenticator.setEnrollmentId(authenticatorOptions.get("enrollmentId"));
phoneAuthenticator.setMethodType("sms");

// build password authenticator challenge request
ChallengeRequest phoneAuthenticatorChallengeRequest = ChallengeRequestBuilder.builder()
        .withAuthenticator(phoneAuthenticator)
        .withStateHandle(stateHandle)
        .build();
idxResponse = remediationOption.proceed(client, phoneAuthenticatorChallengeRequest);

// check remediation options to continue the flow
remediationOptions = idxResponse.remediation().remediationOptions();
remediationOptionsOptional = Arrays.stream(remediationOptions)
        .filter(x -> "challenge-authenticator".equals(x.getName()))
        .findFirst();
remediationOption = remediationOptionsOptional.get();

// answer password authenticator challenge
Credentials credentials = new Credentials();

// code received via sms or voice
credentials.setPasscode("code".toCharArray());

// build answer password authenticator challenge request
AnswerChallengeRequest phoneSmsCodeAuthenticatorAnswerChallengeRequest = AnswerChallengeRequestBuilder.builder()
        .withStateHandle(stateHandle)
        .withCredentials(credentials)
        .build();
idxResponse = remediationOption.proceed(client, phoneSmsCodeAuthenticatorAnswerChallengeRequest);

// check remediation options to continue the flow
remediationOptions = idxResponse.remediation().remediationOptions();
remediationOptionsOptional = Arrays.stream(remediationOptions)
        .filter(x -> "select-authenticator-authenticate".equals(x.getName()))
        .findFirst();
remediationOption = remediationOptionsOptional.get();

// get authenticator options
authenticatorOptions = remediationOption.getAuthenticatorOptions();
log.info("Authenticator Options: {}", authenticatorOptions);

// select password authenticator
Authenticator passwordAuthenticator = new Authenticator();
passwordAuthenticator.setId(authenticatorOptions.get("password"));
passwordAuthenticator.setMethodType("password");

// build password authenticator challenge request
ChallengeRequest passwordAuthenticatorChallengeRequest = ChallengeRequestBuilder.builder()
        .withAuthenticator(passwordAuthenticator)
        .withStateHandle(stateHandle)
        .build();
idxResponse = remediationOption.proceed(client, passwordAuthenticatorChallengeRequest);

// check remediation options to continue the flow
remediationOptions = idxResponse.remediation().remediationOptions();
remediationOptionsOptional = Arrays.stream(remediationOptions)
        .filter(x -> "challenge-authenticator".equals(x.getName()))
        .findFirst();
remediationOption = remediationOptionsOptional.get();

// answer password authenticator challenge
credentials = new Credentials();
credentials.setPasscode("{password}".toCharArray());

// build answer password authenticator challenge request
AnswerChallengeRequest passwordAuthenticatorAnswerChallengeRequest = AnswerChallengeRequestBuilder.builder()
        .withStateHandle(stateHandle)
        .withCredentials(credentials)
        .build();
idxResponse = remediationOption.proceed(client, passwordAuthenticatorAnswerChallengeRequest);

// check if we landed success on login
if (idxResponse.isLoginSuccessful()) {
    log.info("Login Successful!");
    TokenResponse tokenResponse = idxResponse.getSuccessWithInteractionCode().exchangeCode(client, idxClientContext);
    log.info("Exchanged interaction code for token: \naccessToken: {}, \nidToken: {}, \nrefreshToken: {}, \ntokenType: {}, \nscope: {}, \nexpiresIn:{}",
            tokenResponse.getAccessToken(),
            tokenResponse.getIdToken(),
            tokenResponse.getRefreshToken(),
            tokenResponse.getTokenType(),
            tokenResponse.getScope(),
            tokenResponse.getExpiresIn());
}

Login using password + web authenticator

In this example, the Org is configured with fingerprint as a second authenticator. After answering the password challenge, users have to provide their fingerprint to finish the process.

Refer here for information on how to extract the assertion data from browser.

Note: Steps to identify the user might change based on your Org configuration.

// get client context
IDXClientContext idxClientContext = client.interact();

// exchange interactHandle for stateHandle
IDXResponse idxResponse = client.introspect(idxClientContext);
String stateHandle = idxResponse.getStateHandle();

// check remediation options to continue the flow
RemediationOption[] remediationOptions = idxResponse.remediation().remediationOptions();
Optional<RemediationOption> remediationOptionsOptional = Arrays.stream(remediationOptions)
        .findFirst();
RemediationOption remediationOption = remediationOptionsOptional.get();
FormValue[] formValues = remediationOption.form();
IdentifyRequest identifyRequest = IdentifyRequestBuilder.builder()
        .withIdentifier("{identifier}") // email
        .withStateHandle(stateHandle)
        .build();

// identify
idxResponse = remediationOption.proceed(client, identifyRequest);

// get remediation options to go to the next step
remediationOptions = idxResponse.remediation().remediationOptions();
remediationOptionsOptional = Arrays.stream(remediationOptions)
        .filter(x -> "select-authenticator-authenticate".equals(x.getName()))
        .findFirst();
remediationOption = remediationOptionsOptional.get();

// get authenticator options
Map<String, String> authenticatorOptions = remediationOption.getAuthenticatorOptions();
log.info("Authenticator Options: {}", authenticatorOptions);

// select password authenticator
Authenticator phoneAuthenticator = new Authenticator();
phoneAuthenticator.setId(authenticatorOptions.get("password"));
phoneAuthenticator.setMethodType("password");

// build password authenticator challenge request
ChallengeRequest phoneAuthenticatorChallengeRequest = ChallengeRequestBuilder.builder()
        .withAuthenticator(phoneAuthenticator)
        .withStateHandle(stateHandle)
        .build();
idxResponse = remediationOption.proceed(client, phoneAuthenticatorChallengeRequest);

// check remediation options to continue the flow
remediationOptions = idxResponse.remediation().remediationOptions();
remediationOptionsOptional = Arrays.stream(remediationOptions)
        .filter(x -> "challenge-authenticator".equals(x.getName()))
        .findFirst();
remediationOption = remediationOptionsOptional.get();

// answer password authenticator challenge
Credentials credentials = new Credentials();
credentials.setPasscode("{password}".toCharArray());

// build answer password authenticator challenge request
AnswerChallengeRequest phoneSmsCodeAuthenticatorAnswerChallengeRequest = AnswerChallengeRequestBuilder.builder()
        .withStateHandle(stateHandle)
        .withCredentials(credentials)
        .build();
idxResponse = remediationOption.proceed(client, phoneSmsCodeAuthenticatorAnswerChallengeRequest);

// check remediation options to continue the flow
remediationOptions = idxResponse.remediation().remediationOptions();
remediationOptionsOptional = Arrays.stream(remediationOptions)
        .filter(x -> "select-authenticator-authenticate".equals(x.getName()))
        .findFirst();
remediationOption = remediationOptionsOptional.get();

// get authenticator options
authenticatorOptions = remediationOption.getAuthenticatorOptions();
log.info("Authenticator Options: {}", authenticatorOptions);

// select webauthn (fingerprint) authenticator
Authenticator webauthnAuthenticator = new Authenticator();
webauthnAuthenticator.setId(authenticatorOptions.get("webauthn"));
webauthnAuthenticator.setMethodType("webauthn");

// build fingerprint authenticator challenge request
ChallengeRequest fingerprintAuthenticatorChallengeRequest = ChallengeRequestBuilder.builder()
        .withAuthenticator(webauthnAuthenticator)
        .withStateHandle(stateHandle)
        .build();
idxResponse = remediationOption.proceed(client, fingerprintAuthenticatorChallengeRequest);

// check remediation options to continue the flow
remediationOptions = idxResponse.remediation().remediationOptions();
remediationOptionsOptional = Arrays.stream(remediationOptions)
        .filter(x -> "challenge-authenticator".equals(x.getName()))
        .findFirst();
remediationOption = remediationOptionsOptional.get();

// build answer fingerprint authenticator challenge request
credentials = new Credentials();

// replace (extract this data from browser and supply it here)
credentials.setAuthenticatorData("");

// replace (extract this data from browser and supply it here)
credentials.setClientData("");

// replace (extract this data from browser and supply it here)
credentials.setSignatureData("");
AnswerChallengeRequest fingerprintAuthenticatorAnswerChallengeRequest = AnswerChallengeRequestBuilder.builder()
        .withStateHandle(stateHandle)
        .withCredentials(credentials)
        .build();
idxResponse = remediationOption.proceed(client, fingerprintAuthenticatorAnswerChallengeRequest);

// check if we landed success on login
if (idxResponse.isLoginSuccessful()) {
    log.info("Login Successful!");
    TokenResponse tokenResponse = idxResponse.getSuccessWithInteractionCode().exchangeCode(client, idxClientContext);
    log.info("Exchanged interaction code for token: \naccessToken: {}, \nidToken: {}, \nrefreshToken: {}, \ntokenType: {}, \nscope: {}, \nexpiresIn:{}",
            tokenResponse.getAccessToken(),
            tokenResponse.getIdToken(),
            tokenResponse.getRefreshToken(),
            tokenResponse.getTokenType(),
            tokenResponse.getScope(),
            tokenResponse.getExpiresIn());
}

Login using password after password reset

In this example, the Org is configured to require password authenticator to login, with no additional authenticators. After sending the identify request with the username, the user can reset the password, after answering the security question. Login will be successful after password reset.

Note: Steps to identify the user might change based on your Org configuration.

// get client context
IDXClientContext idxClientContext = client.interact();

// exchange interactHandle for stateHandle
IDXResponse idxResponse = client.introspect(idxClientContext);
String stateHandle = idxResponse.getStateHandle();

// check remediation options to continue the flow
RemediationOption[] remediationOptions = idxResponse.remediation().remediationOptions();
Optional<RemediationOption> remediationOptionsOptional = Arrays.stream(remediationOptions)
        .findFirst();
RemediationOption remediationOption = remediationOptionsOptional.get();
FormValue[] formValues = remediationOption.form();
IdentifyRequest identifyRequest = IdentifyRequestBuilder.builder()
        .withIdentifier("{identifier}") // email
        .withStateHandle(stateHandle)
        .build();

// identify
idxResponse = remediationOption.proceed(client, identifyRequest);

// start the password recovery/reset flow
RecoverRequest recoverRequest = RecoverRequestBuilder.builder()
        .withStateHandle(stateHandle)
        .build();
idxResponse = remediationOption.proceed(client, recoverRequest);

// since the org requires password only, we don't have the "select password authenticator" step as in previous examples
remediationOptions = idxResponse.remediation().remediationOptions();
remediationOptionsOptional = Arrays.stream(remediationOptions)
        .filter(x -> "challenge-authenticator".equals(x.getName()))
        .findFirst();
remediationOption = remediationOptionsOptional.get();

// answer the security question authenticator which required to reset password
Credentials secQnEnrollmentCredentials = new Credentials();

// e.g. "favorite_sports_player"
secQnEnrollmentCredentials.setQuestionKey("{questionKey}");

// e.g. "Tiger Woods"
secQnEnrollmentCredentials.setAnswer("{answer}".toCharArray());

// build answer authenticator challenge request
AnswerChallengeRequest passwordAuthenticatorAnswerChallengeRequest = AnswerChallengeRequestBuilder.builder()
        .withStateHandle(stateHandle)
        .withCredentials(secQnEnrollmentCredentials)
        .build();
idxResponse = remediationOption.proceed(client, passwordAuthenticatorAnswerChallengeRequest);

// select the "reset-authenticator" remediation option to set the new password
remediationOptions = idxResponse.remediation().remediationOptions();
remediationOptionsOptional = Arrays.stream(remediationOptions)
        .filter(x -> "reset-authenticator".equals(x.getName()))
        .findFirst();
remediationOption = remediationOptionsOptional.get();

// set passcode to your new password value
Credentials credentials = new Credentials();
credentials.setPasscode("{new_password}".toCharArray());

// build answer password authenticator challenge request
passwordAuthenticatorAnswerChallengeRequest = AnswerChallengeRequestBuilder.builder()
        .withStateHandle(stateHandle)
        .withCredentials(credentials)
        .build();
idxResponse = remediationOption.proceed(client, passwordAuthenticatorAnswerChallengeRequest);

// check if we landed success on login
if (idxResponse.isLoginSuccessful()) {
    log.info("Login Successful!");
    // exchange the received interaction code for a token
    TokenResponse tokenResponse = idxResponse.getSuccessWithInteractionCode().exchangeCode(client, idxClientContext);
    log.info("Exchanged interaction code for token: \naccessToken: {}, \nidToken: {}, \nrefreshToken: {}, \ntokenType: {}, \nscope: {}, \nexpiresIn:{}",
            tokenResponse.getAccessToken(),
            tokenResponse.getIdToken(),
            tokenResponse.getRefreshToken(),
            tokenResponse.getTokenType(),
            tokenResponse.getScope(),
            tokenResponse.getExpiresIn());
}

User Enrollment - Registration and progressive profiling

Enroll a user with additional profile attributes.

UserProfile userProfile = new UserProfile();
userProfile.addAttribute("key-1", "value-1");
userProfile.addAttribute("key-2", "value-2");
EnrollUserProfileUpdateRequest enrollUserProfileUpdateRequest = EnrollUserProfileUpdateRequestBuilder.builder()
        .withStateHandle("{stateHandle}")
        .withUserProfile(userProfile)
        .build();
IDXResponse idxResponse = remediationOption.proceed(client, enrollUserProfileUpdateRequest);

Registration Flow - New User Registration

Sign up a new user.

// get client context
IDXClientContext idxClientContext = client.interact();

// exchange interactHandle for stateHandle
IDXResponse idxResponse = client.introspect(idxClientContext);
String stateHandle = idxResponse.getStateHandle();

// get remediation options to go to the next step
RemediationOption[] remediationOptions = idxResponse.remediation().remediationOptions();
Optional<RemediationOption> remediationOptionsOptional = Arrays.stream(remediationOptions)
        .filter(x -> "select-enroll-profile".equals(x.getName()))
        .findFirst();
RemediationOption remediationOption = remediationOptionsOptional.get();
EnrollRequest enrollRequest = EnrollRequestBuilder.builder()
        .withStateHandle(stateHandle)
        .build();

// enroll new user
idxResponse = remediationOption.proceed(client, enrollRequest);

// get remediation options to go to the next step
remediationOptions = idxResponse.remediation().remediationOptions();
remediationOptionsOptional = Arrays.stream(remediationOptions)
        .filter(x -> "enroll-profile".equals(x.getName()))
        .findFirst();
remediationOption = remediationOptionsOptional.get();

// supply only the "required" attributes
UserProfile up = new UserProfile();

// replace
up.addAttribute("lastName", "Coder");

// replace
up.addAttribute("firstName", "Joe");
Random randomGenerator = new Random();
int randomInt = randomGenerator.nextInt(1000);

// replace
up.addAttribute("email", "joe.coder" + randomInt + "@example.com");

// replace
up.addAttribute("age", "40");

// replace
up.addAttribute("sex", "Male");
EnrollUserProfileUpdateRequest enrollUserProfileUpdateRequest = EnrollUserProfileUpdateRequestBuilder.builder()
        .withUserProfile(up)
        .withStateHandle(stateHandle)
        .build();
idxResponse = remediationOption.proceed(client, enrollUserProfileUpdateRequest);

// check remediation options to go to the next step
remediationOptions = idxResponse.remediation().remediationOptions();
Optional<RemediationOption> remediationOptionsSelectAuthenticatorOptional = Arrays.stream(remediationOptions)
        .filter(x -> "select-authenticator-enroll".equals(x.getName()))
        .findFirst();
remediationOption = remediationOptionsSelectAuthenticatorOptional.get();
Map<String, String> authenticatorOptions = remediationOption.getAuthenticatorOptions();

// select an authenticator (sec qn in this case)
Authenticator secQnEnrollmentAuthenticator = new Authenticator();
secQnEnrollmentAuthenticator.setId(authenticatorOptions.get("security_question"));
secQnEnrollmentAuthenticator.setMethodType("security_question");

// build enroll request
enrollRequest = EnrollRequestBuilder.builder()
        .withAuthenticator(secQnEnrollmentAuthenticator)
        .withStateHandle(stateHandle)
        .build();

// proceed
idxResponse = remediationOption.proceed(client, enrollRequest);

// get remediation options to go to the next step
remediationOptions = idxResponse.remediation().remediationOptions();
Optional<RemediationOption> remediationOptionsEnrollAuthenticatorOptional = Arrays.stream(remediationOptions)
        .filter(x -> "enroll-authenticator".equals(x.getName()))
        .findFirst();
remediationOption = remediationOptionsEnrollAuthenticatorOptional.get();
FormValue[] enrollAuthenticatorFormValues = remediationOption.form();
Optional<FormValue> enrollAuthenticatorFormOptional = Arrays.stream(enrollAuthenticatorFormValues)
        .filter(x -> "credentials".equals(x.getName()))
        .findFirst();
FormValue enrollAuthenticatorForm = enrollAuthenticatorFormOptional.get();
Options[] enrollmentAuthenticatorOptions = enrollAuthenticatorForm.options();
Optional<Options> chooseSecQnOptionOptional = Arrays.stream(enrollmentAuthenticatorOptions)
        .filter(x -> "Choose a security question".equals(x.getLabel()))
        .findFirst();

// view default security questions list
Options choseSecQnOption = chooseSecQnOptionOptional.get();
Credentials secQnEnrollmentCredentials = new Credentials();

// chosen one from the above list
secQnEnrollmentCredentials.setQuestionKey("disliked_food");
secQnEnrollmentCredentials.setQuestion("What is the food you least liked as a child?");
secQnEnrollmentCredentials.setAnswer("{answer}".toCharArray());
AnswerChallengeRequest answerChallengeRequest = AnswerChallengeRequestBuilder.builder()
        .withStateHandle(stateHandle)
        .withCredentials(secQnEnrollmentCredentials)
        .build();

// proceed
idxResponse = remediationOption.proceed(client, answerChallengeRequest);

// check remediation options to go to the next step
remediationOptions = idxResponse.remediation().remediationOptions();
remediationOptionsSelectAuthenticatorOptional = Arrays.stream(remediationOptions)
        .filter(x -> "select-authenticator-enroll".equals(x.getName()))
        .findFirst();
remediationOption = remediationOptionsSelectAuthenticatorOptional.get();
authenticatorOptions = remediationOption.getAuthenticatorOptions();

// select an authenticator (email in this case)
Authenticator emailAuthenticator = new Authenticator();
emailAuthenticator.setId(authenticatorOptions.get("email"));
emailAuthenticator.setMethodType("email");

// build enroll request
enrollRequest = EnrollRequestBuilder.builder()
        .withAuthenticator(emailAuthenticator)
        .withStateHandle(stateHandle)
        .build();

// proceed
idxResponse = remediationOption.proceed(client, enrollRequest);

// get remediation options to go to the next step
remediationOptions = idxResponse.remediation().remediationOptions();
remediationOptionsEnrollAuthenticatorOptional = Arrays.stream(remediationOptions)
        .filter(x -> "enroll-authenticator".equals(x.getName()))
        .findFirst();
remediationOption = remediationOptionsEnrollAuthenticatorOptional.get();
enrollAuthenticatorFormValues = remediationOption.form();
enrollAuthenticatorFormOptional = Arrays.stream(enrollAuthenticatorFormValues)
        .filter(x -> "credentials".equals(x.getName()))
        .findFirst();

// enter passcode received in email
Scanner in = new Scanner(System.in, "UTF-8");
log.info("Enter Email Passcode: ");
String emailPasscode = in.nextLine();
Credentials credentials = new Credentials();
credentials.setPasscode(emailPasscode.toCharArray());
answerChallengeRequest = AnswerChallengeRequestBuilder.builder()
        .withStateHandle(stateHandle)
        .withCredentials(credentials)
        .build();

// proceed
idxResponse = remediationOption.proceed(client, answerChallengeRequest);

// check remediation options to go to the next step
remediationOptions = idxResponse.remediation().remediationOptions();
remediationOptionsSelectAuthenticatorOptional = Arrays.stream(remediationOptions)
        .filter(x -> "select-authenticator-enroll".equals(x.getName()))
        .findFirst();
remediationOption = remediationOptionsSelectAuthenticatorOptional.get();
authenticatorOptions = remediationOption.getAuthenticatorOptions();

// select an authenticator (password in this case)
Authenticator passwordAuthenticator = new Authenticator();
passwordAuthenticator.setId(authenticatorOptions.get("password"));
passwordAuthenticator.setMethodType("password");

// build enroll request
enrollRequest = EnrollRequestBuilder.builder()
        .withAuthenticator(passwordAuthenticator)
        .withStateHandle(stateHandle)
        .build();

// proceed
idxResponse = remediationOption.proceed(client, enrollRequest);

// get remediation options to go to the next step
remediationOptions = idxResponse.remediation().remediationOptions();
remediationOptionsEnrollAuthenticatorOptional = Arrays.stream(remediationOptions)
        .filter(x -> "enroll-authenticator".equals(x.getName()))
        .findFirst();
remediationOption = remediationOptionsEnrollAuthenticatorOptional.get();
enrollAuthenticatorFormValues = remediationOption.form();
enrollAuthenticatorFormOptional = Arrays.stream(enrollAuthenticatorFormValues)
        .filter(x -> "credentials".equals(x.getName()))
        .findFirst();
credentials = new Credentials();
credentials.setPasscode("password".toCharArray());
answerChallengeRequest = AnswerChallengeRequestBuilder.builder()
        .withStateHandle(stateHandle)
        .withCredentials(credentials)
        .build();

// proceed
idxResponse = remediationOption.proceed(client, answerChallengeRequest);

// get remediation options to go to the next step
remediationOptions = idxResponse.remediation().remediationOptions();
Optional<RemediationOption> skipAuthenticatorEnrollmentOptional = Arrays.stream(remediationOptions)
        .filter(x -> "skip".equals(x.getName()))
        .findFirst();
remediationOption = skipAuthenticatorEnrollmentOptional.get();
SkipAuthenticatorEnrollmentRequest skipAuthenticatorEnrollmentRequest = SkipAuthenticatorEnrollmentRequestBuilder.builder()
        .withStateHandle(stateHandle)
        .build();

// proceed with skipping optional authenticator enrollment
idxResponse = remediationOption.proceed(client, skipAuthenticatorEnrollmentRequest);

// This response should contain the interaction code
if (idxResponse.isLoginSuccessful()) {
    log.info("Login Successful!");
    TokenResponse tokenResponse = idxResponse.getSuccessWithInteractionCode().exchangeCode(client, idxClientContext);
    log.info("Exchanged interaction code for token: \naccessToken: {}, \nidToken: {}, \nrefreshToken: {}, \ntokenType: {}, \nscope: {}, \nexpiresIn:{}",
            tokenResponse.getAccessToken(),
            tokenResponse.getIdToken(),
            tokenResponse.getRefreshToken(),
            tokenResponse.getTokenType(),
            tokenResponse.getScope(),
            tokenResponse.getExpiresIn());
}

Print Raw Response

String rawResponse = idxResponse.raw();

Thread Safety

Every instance of the SDK Client is thread-safe. You should use the same instance throughout the entire lifecycle of your application. Each instance has its own Connection pool and Caching resources that are automatically released when the instance is garbage collected.

Configuration Reference

This library looks for configuration in the following sources:

  1. An okta.yaml at the root of the applications classpath
  2. An okta.yaml file in a .okta folder in the current user's home directory (~/.okta/okta.yaml or %userprofile%\.okta\okta.yaml)
  3. Environment variables
  4. Java System Properties
  5. Configuration explicitly set programmatically (see the example in Getting started)

Higher numbers win. In other words, configuration passed via the constructor will override configuration found in environment variables, which will override configuration in okta.yaml (if any), and so on.

YAML configuration

The full YAML configuration looks like:

okta:
  idx:
    issuer: "https://{yourOktaDomain}/oauth2/{authorizationServerId}" # e.g. https://foo.okta.com/oauth2/default, https://foo.okta.com/oauth2/ausar5vgt5TSDsfcJ0h7
    clientId: "{clientId}"
    clientSecret: "{clientSecret}" # Required for confidential clients
    scopes:
    - "{scope1}"
    - "{scope2}"
    redirectUri: "{redirectUri}"

Here's an example config file

okta:
  idx:
    issuer: "https://dev-1234.okta.com/oauth2/default"
    clientId: "123xyz"
    clientSecret: "123456abcxyz" # Required for confidential clients
    scopes:
    - "openid"
    - "profile"
    - "offline_access"
    redirectUri: "https://loginredirect.com"

Environment variables

Each one of the configuration values above can be turned into an environment variable name with the _ (underscore) character:

  • OKTA_IDX_ISSUER
  • OKTA_IDX_CLIENTID
  • OKTA_IDX_CLIENTSECRET
  • OKTA_IDX_SCOPES
  • OKTA_IDX_REDIRECTURI

System properties

Each one of the configuration values written in 'dot' notation to be used as a Java system property:

  • okta.idx.issuer
  • okta.idx.clientId
  • okta.idx.clientSecret
  • okta.idx.scopes
  • okta.idx.redirectUri

Building the SDK

In most cases, you won't need to build the SDK from source. If you want to build it yourself, clone the repo and run mvn install.

Contributing

We are happy to accept contributions and PRs! Please see the contribution guide to understand how to structure a contribution.

com.okta.idx.sdk

Okta, Inc

Versions

Version
0.1.0-beta.5
0.1.0-beta.4
0.1.0-beta.3
0.1.0-beta.2
0.1.0-beta.1