Access Management

How to follow this chapter

Inputs: clientEmail, clientPassword and the delivered verification code.

Steps and expected result: First factor requests verification; second factor returns the client session. Store both tokens after login or refresh. Logout ends the current session.

Alternative or later actions: Use the login recipe when starting a session; refresh and logout are later session-management actions.

DTO source: AccessTokenOutputModel, AuthenticationSecondFactorOutputModel

Variables assigned in an earlier block are reused by later blocks. Client-entered values come from your UI; keep identifiers and lookup results in the current client context. Do not create a fresh context between steps.

The snippets use shared helper classes. See Shared helper classes: complete C# and Java source before copying an example.
Before you start: An existing clientEmail and clientPassword. Start login with a GUEST token and collect authenticationCode from the configured second-factor channel.
Keep for the next step: The userId and current accessToken, refreshToken and validUntil. Save both new tokens after every refresh.
In this section, we will look at how to perform two-factor authentication. The result of this process is an authorization token that contains the role of the user (CLIENT, SELLER, ADMIN, SUPERADMIN). Store AccessToken, RefreshToken, Id and ValidUntil from the response. The expiry and refresh window depend on the service configuration.

Two-factor authentication

Inputs: Client email and password, then the delivered authenticationCode.
Action: Complete the two authentication factors.
Keep / expected result: Store userId, accessToken, refreshToken and validUntil from the final response.
For this operation, the call must have the authorization HTTP header set with the GUEST token. After successful first-factor authentication, collect the verification code from the channel configured for the account. In isolated test configurations, the response can also contain the code. For the client account shown here, successful second-factor authentication returns a CLIENT access token for subsequent client requests.
								
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", guestToken);
// First factor authentication
var payload = JsonConvert.SerializeObject(new { AccountNumber = clientEmail, Password = clientPassword });
(await httpClient.PostAsync("api/v1/users/authentication/first-factor", new StringContent(payload, Encoding.UTF8, "application/json"))).EnsureSuccessStatusCode();
// Collect authenticationCode from the configured second-factor channel.
// Second factor authentication
payload = JsonConvert.SerializeObject(new { AccountNumber = clientEmail, Token = authenticationCode });
var httpResponseMessage = await httpClient.PostAsync("api/v1/users/authentication/second-factor", new StringContent(payload, Encoding.UTF8, "application/json"));
httpResponseMessage.EnsureSuccessStatusCode();
var resultAsString = await httpResponseMessage.Content.ReadAsStringAsync();
var authenticationResult = JsonConvert.DeserializeObject<AuthenticationSecondFactorOutputModel>(resultAsString);
// Now, set the CLIENT token for the next requests
var userId = authenticationResult.Id;
var accessToken = authenticationResult.AccessToken;
var refreshToken = authenticationResult.RefreshToken;
var validUntil = authenticationResult.ValidUntil;
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

								
// First factor authentication
JsonObject firstFactorPayload = new JsonObject();
firstFactorPayload.addProperty("AccountNumber", clientEmail);
firstFactorPayload.addProperty("Password", clientPassword);

HttpRequest firstFactorRequest = HttpRequest.newBuilder()
    .timeout(Duration.ofMinutes(1))
    .uri(Settings.wmsUri("users/authentication/first-factor"))
    .header("Accept-Language", "de-DE")
    .header("Authorization", "Bearer " + Settings.GUEST_TOKEN)
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(firstFactorPayload.toString()))
    .build();

var firstFactorResponse = httpClient.send(firstFactorRequest, BodyHandlers.ofString());
Api.requireSuccess(firstFactorResponse);
// Collect authenticationCode from the configured second-factor channel.

// Second factor authentication
JsonObject secondFactorPayload = new JsonObject();
secondFactorPayload.addProperty("AccountNumber", clientEmail);
secondFactorPayload.addProperty("Token", authenticationCode);

HttpRequest secondFactorRequest = HttpRequest.newBuilder()
    .timeout(Duration.ofMinutes(1))
    .uri(Settings.wmsUri("users/authentication/second-factor"))
    .header("Accept-Language", "de-DE")
    .header("Authorization", "Bearer " + Settings.GUEST_TOKEN)
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(secondFactorPayload.toString()))
    .build();

var response = httpClient.send(secondFactorRequest, BodyHandlers.ofString());
Api.requireSuccess(response);
AuthenticationSecondFactorOutputModel authenticationResult = new Gson().fromJson(response.body(), AuthenticationSecondFactorOutputModel.class);

// Now, set the CLIENT token for the next requests
var userId = authenticationResult.Id;
var accessToken = authenticationResult.AccessToken;
var refreshToken = authenticationResult.RefreshToken;
var validUntil = authenticationResult.ValidUntil;

The second-factor delivery channel is configured for the account and environment. Use the code received by the client.

Logout

Inputs: The current CLIENT access token.
Action: End the current session.
Keep / expected result: Clear the stored session after successful logout.
This process invalidates the authentication token.
								
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
(await httpClient.PostAsync("api/v1/users/authentication/logout", null)).EnsureSuccessStatusCode();

								
HttpRequest theRequest = HttpRequest.newBuilder()
    .timeout(Duration.ofMinutes(1))
    .uri(Settings.wmsUri("users/authentication/logout"))
    .header("Accept-Language", "de-DE")
    .header("Authorization", "Bearer " + accessToken)
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.noBody())
    .build();

var response = httpClient.send(theRequest, BodyHandlers.ofString());
Api.requireSuccess(response);

If a token has become invalid due to a logout, it can no longer be updated: a new login is required.

Refresh authentication token

Inputs: userId and the current access/refresh token pair.
Action: Request a refreshed session.
Keep / expected result: Replace both stored tokens and validUntil with the returned values.
When the access token expires, protected requests return HTTP 401; the service may include TOKEN-EXPIRED: true. Refresh requires the current access/refresh token pair and user ID. The permitted refresh lifetime is configured from the stored token creation time; it is not a fixed five-minute window after expiry.
Authorize the refresh request with a valid GUEST token. Send the current access/refresh pair in the payload, then replace both saved tokens with the returned pair. If refresh fails, repeat login; do not repeatedly retry an invalid pair.

httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", guestToken);
payload = JsonConvert.SerializeObject(new
{
    UserId = userId,
    RefreshToken = refreshToken,
    AccessToken = accessToken
});
httpResponseMessage = await httpClient.PostAsync("api/v1/users/authentication/access-token/refresh", new StringContent(payload, Encoding.UTF8, "application/json"));
httpResponseMessage.EnsureSuccessStatusCode();
resultAsString = await httpResponseMessage.Content.ReadAsStringAsync();
var accessTokenOutputModel = JsonConvert.DeserializeObject<AccessTokenOutputModel>(resultAsString);
// Set new CLIENT token for the next HTTP requests
accessToken = accessTokenOutputModel.AccessToken;
refreshToken = accessTokenOutputModel.RefreshToken;
validUntil = accessTokenOutputModel.ValidUntil;
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

								
JsonObject thePayload = new JsonObject();
thePayload.addProperty("UserId", userId);
thePayload.addProperty("RefreshToken", refreshToken);
thePayload.addProperty("AccessToken", accessToken);

HttpRequest theRequest = HttpRequest.newBuilder()
    .timeout(Duration.ofMinutes(1))
    .uri(Settings.wmsUri("users/authentication/access-token/refresh"))
    .header("Accept-Language", "de-DE")
    .header("Authorization", "Bearer " + Settings.GUEST_TOKEN)
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(thePayload.toString()))
    .build();

var response = httpClient.send(theRequest, BodyHandlers.ofString());
Api.requireSuccess(response);
AccessTokenOutputModel accessTokenOutputModel = new Gson().fromJson(response.body(), AccessTokenOutputModel.class);

// New CLIENT token for the next HTTP requests
accessToken = accessTokenOutputModel.AccessToken;
refreshToken = accessTokenOutputModel.RefreshToken;
validUntil = accessTokenOutputModel.ValidUntil;

Recognize the result

After second-factor authentication or refresh: replace the stored token pair. This is a projection of the response.

{"id":123,"accessToken":"<access-token>","refreshToken":"<refresh-token>","validUntil":"2026-10-13T12:00:00Z"}

The DTO catalog contains the remaining response properties. A failed request must be handled before advancing to the next step; see Error handling.