This blog post is an additional add-on to Dany’s post on Office 365 / Azure AD seemless Users and Password’s Migration to Okta
In this post, we will replace the Okta Workflows implementation of the hook endpoint with a Java service.

Webhooks are user-defined HTTP callbacks and are usually triggered by some event.
Okta’s implementation of webhooks is quite extensive and provides for two distinct types:
- Inline Hooks – Inline hooks are outbound calls from Okta to your own custom code, triggered at specific points in Okta process flows. They allow you to integrate custom functionality into those flows. You implement your custom code as a web service with an Internet-accessible endpoint. It’s your responsibility to arrange hosting of your code on a system external to Okta. Okta defines the REST API contract for the requests it sends to your custom code, as well as for the responses your custom code can send back. The outbound call from Okta is called a hook. Your code, which receives the call, is referred to as your external service.Inline hooks use synchronous calls, which means that the Okta process that triggered the hook is paused until a response from your external service is received.
- Event Hooks – Event Hooks are outbound calls from Okta, sent when specified events occur in your org. They take the form of HTTPS REST calls to a URL you specify, encapsulating information about the events in JSON objects in the request body. These calls from Okta are meant to be used as triggers for process flows within your own software systems.
Okta currently supports the following Inline Hooks:
Name | Description |
---|---|
Token Inline Hook | Customizes tokens returned by Okta API Access Management |
User Import Inline Hook | Adds custom logic to the user import process |
SAML Assertion Inline Hook | Customizes SAML assertions returned by Okta |
Registration Inline Hook | Customizes handling of Self-Service Registration (SSR) and Progressive Enrollment support |
Password Import Inline Hook | Verifies a user-supplied password to support migration of users to Okta |
Telephony Inline Hook | Customizes Okta’s flows that send SMS or Voice messages |
The Password Import Inline Hook enables migration of users from another data store in a case where you wish the users to retain their current passwords. The Password Import Inline Hook is triggered when the end user tries to sign in to Okta for the first time. Okta sends your external service the password that the user supplied. Your external service then needs to send a response to Okta indicating whether the password supplied by the end user is valid or not. If it is valid, then Okta will set that password within Okta’s Universal Directory (UD). All subsequent authentications will be directly against UD.
This process is represented by the following diagram:

The sequence is as follows:
- The User authenticates against Okta for the first time
- Okta checks to see that the password has not been set
- Web Hook sent to external service to validate the password
- Password validated against existing system (Azure AD)
- Validation response sent back to Okta
- If successful, Okta password is set
- User successfully authenticated
Note: The Password Import Hook will not trigger unless the user has a status of STAGED and a credential of type IMPORT as displayed below.
"credentials": {
"password": {},
"provider": {
"type": "IMPORT",
"name": "IMPORT"
}
},
External Service Implementation
As per Dany’s post here Office 365 / Azure AD seemless Users and Password’s Migration to Okta , we can see that Okta Workflows can provide a quick and easy way to implement that external service. As Okta Workflows is inherently an asynchronous processing engine, there is no guarantee of response time. (Although this may change in the future). For a small number of users, any latency may be negligible, but for a large number of users (many thousands), it may become unsuitable for this use case. The external service should be written an a way that scales and can handle a large throughput. So as an alternative, this blog entry details how to implement this service in Java.

To do this, my example uses the jersey libraries. Jersey RESTful Web Services, formerly Glassfish Jersey, currently Eclipse Jersey framework is an open source framework for developing RESTful Web Services in Java. To expose your java class as an external endpoint, there is minimal configuration. All that is required is the following entries to your web.xml descriptor file:
<servlet>
<servlet-name>Jersey RESTful Application</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com.iamse</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Jersey RESTful Application</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
The basic structure of my code is the following:
@Path("/PasswordMigration")
public class PasswordMigration
{
public PasswordMigration() {}
@POST
@Path("/verify")
@Consumes({"application/json"})
public Response verify(String message) {
logger.debug("PasswordMigration/verify Service called via POST ...");
...
}
The above code and configuration results in my service being available at the following endpoint:
https://localhost:8443/<project_name>/rest/PasswordMigration/verify
Now lets looks at the basic structure of my class.
Class Structure
Read Property File
The two values extracted into an external property file are:
<code>######## Runtime Properties ######### token_endpoint=https://login.microsoftonline.com/<replace with tenent id>/oauth2/v2.0/token </code>
<code>client_id=<replace with client id></code>
Load Request Parameters
We start by extracting the users credentials from the incoming message payload and then formatting the outgoing payload to be sent to Azure.
// Load request parameters, including the passed username and password
JSONObject jsonObject = new JSONObject(message);
HashMap<String, String> params = new HashMap<String, String>();
params.put("grant_type", "password");
params.put("username", jsonObject.getJSONObject("data").getJSONObject("context").getJSONObject("credential").getString("username"));
params.put("client_id", props.getProperty("client_id"));
params.put("password", jsonObject.getJSONObject("data").getJSONObject("context").getJSONObject("credential").getString("password"));
params.put("scope", "openid");
// Encode the request parameters
try {
String data = getDataString(params);
postData = data.getBytes(StandardCharsets.UTF_8);
postDataLength = postData.length;
}
catch (UnsupportedEncodingException e) {
return Response.serverError().build();
}
Create Connection and Send Request
Now that we have the payload ready to be sent to Azure, we can create a connection to the token endpoint and post the data.
// Open connection to token endpoint
endpoint = new URL(props.getProperty("token_endpoint"));
logger.debug("About to establish connection to server ...");
connection = (HttpsURLConnection) endpoint.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-type", "application/x-www-form-urlencoded");
connection.setDoOutput(true);
connection.setInstanceFollowRedirects(false);
connection.setRequestProperty("charset", "utf-8");
connection.setRequestProperty("Content-Length", Integer.toString(postDataLength ));
connection.setUseCaches(false);
// Send encoded data
DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
wr.write(postData);
wr.flush();
wr.close();
responseCode = connection.getResponseCode();
logger.debug("POST completed ...");
Process Response
The final section is to process the response from Azure and then send the correctly formatted JSON payload back to Okta.
// Ensure we have a 200 response, otherwise UNVERIFIED will be returned
if (responseCode == 200) {
// Extract response payload
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String inputLine;
StringBuffer getResponse = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
getResponse.append(inputLine);
}
in.close();
// Parse the response
responseObject = new JSONObject(getResponse.toString());
// Ensure the response contains an access token
try {
responseObject.getString("access_token");
logger.debug("Found access_token ...");
// Change response to VERIFIED
jsonResponse = "{\"commands\": [{\"type\": \"com.okta.action.update\", \"value\": {\"credential\": \"VERIFIED\"}}]}";
} catch (Exception e) {} // Ignore exception if not found
}
Finally, the response it returned with this line of code:
return Response.ok(jsonResponse, MediaType.APPLICATION_JSON).build();
This java example can be downloaded from GitHub here: GitHub – iamse-blog/Password-Migration
One thought on “Password Import Hook with Java Endpoint”