Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ private AuthenticationResultSupplier getAuthenticationResultSupplier(MsalRequest
supplier = new AcquireTokenByOnBehalfOfSupplier(
(ConfidentialClientApplication) this,
(OnBehalfOfRequest) msalRequest);
} else if (msalRequest instanceof UserFederatedIdentityCredentialRequest) {
supplier = new AcquireTokenByUserFederatedIdentityCredentialSupplier(
(ConfidentialClientApplication) this,
(UserFederatedIdentityCredentialRequest) msalRequest);
} else if (msalRequest instanceof ManagedIdentityRequest) {
supplier = new AcquireTokenByManagedIdentitySupplier(
(ManagedIdentityApplication) this,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.microsoft.aad.msal4j;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class AcquireTokenByUserFederatedIdentityCredentialSupplier extends AuthenticationResultSupplier {

private static final Logger LOG = LoggerFactory.getLogger(AcquireTokenByUserFederatedIdentityCredentialSupplier.class);
private UserFederatedIdentityCredentialRequest userFicRequest;

AcquireTokenByUserFederatedIdentityCredentialSupplier(ConfidentialClientApplication clientApplication,
UserFederatedIdentityCredentialRequest userFicRequest) {
super(clientApplication, userFicRequest);
this.userFicRequest = userFicRequest;
}

@Override
AuthenticationResult execute() throws Exception {
if (!userFicRequest.parameters.forceRefresh()) {
LOG.debug("ForceRefresh is false. Attempting cache lookup");
try {
SilentParameters parameters = SilentParameters
.builder(this.userFicRequest.parameters.scopes())
.claims(this.userFicRequest.parameters.claims())
.tenant(this.userFicRequest.parameters.tenant())
.build();

RequestContext context = new RequestContext(
this.clientApplication,
PublicApi.ACQUIRE_TOKEN_SILENTLY,
parameters);

SilentRequest silentRequest = new SilentRequest(
parameters,
this.clientApplication,
context,
null);

AcquireTokenSilentSupplier supplier = new AcquireTokenSilentSupplier(
this.clientApplication,
silentRequest);

return supplier.execute();
} catch (MsalClientException ex) {
LOG.debug("Cache lookup failed: {}", ex.getMessage());
return acquireTokenByUserFic();
}
}

LOG.debug("ForceRefresh is true. Skipping cache lookup");
return acquireTokenByUserFic();
}

private AuthenticationResult acquireTokenByUserFic() throws Exception {
AcquireTokenByAuthorizationGrantSupplier supplier = new AcquireTokenByAuthorizationGrantSupplier(
this.clientApplication,
userFicRequest,
null);

return supplier.execute();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,24 @@ public CompletableFuture<IAuthenticationResult> acquireToken(OnBehalfOfParameter
return this.executeRequest(oboRequest);
}

@Override
public CompletableFuture<IAuthenticationResult> acquireToken(UserFederatedIdentityCredentialParameters parameters) {
validateNotNull("parameters", parameters);

RequestContext context = new RequestContext(
this,
PublicApi.ACQUIRE_TOKEN_BY_USER_FEDERATED_IDENTITY_CREDENTIAL,
parameters);

UserFederatedIdentityCredentialRequest userFicRequest =
new UserFederatedIdentityCredentialRequest(
parameters,
this,
context);

return this.executeRequest(userFicRequest);
}

private ConfidentialClientApplication(Builder builder) {
super(builder);
sendX5c = builder.sendX5c;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,14 @@ class GrantConstants {
static final String USERNAME_PARAMETER = "username";
static final String PASSWORD_PARAMETER = "password";

//Parameter names for user_fic flow
static final String USER_FEDERATED_IDENTITY_CREDENTIAL = "user_federated_identity_credential";
static final String USER_ID_PARAMETER = "user_id";

//Grant types
static final String AUTHORIZATION_CODE = "authorization_code";
static final String CLIENT_CREDENTIALS = "client_credentials";
static final String USER_FIC = "user_fic";
static final String PASSWORD = "password";
static final String SAML_2_BEARER = "urn:ietf:params:oauth:grant-type:saml2-bearer";
static final String SAML_1_1_BEARER = "urn:ietf:params:oauth:grant-type:saml1_1-bearer";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,17 @@ public interface IConfidentialClientApplication extends IClientApplicationBase {
* @return {@link CompletableFuture} containing an {@link IAuthenticationResult}
*/
CompletableFuture<IAuthenticationResult> acquireToken(OnBehalfOfParameters parameters);

/**
* Acquires a token using the User Federated Identity Credential (user_fic) flow.
* This is Leg 3 of the agent identity protocol, where a federated identity credential
* (obtained from Leg 2) is exchanged for a user-scoped token.
* <p>
* The user can be identified by either UPN (username) or Object ID, as specified
* in the {@link UserFederatedIdentityCredentialParameters}.
*
* @param parameters instance of {@link UserFederatedIdentityCredentialParameters}
* @return {@link CompletableFuture} containing an {@link IAuthenticationResult}
*/
CompletableFuture<IAuthenticationResult> acquireToken(UserFederatedIdentityCredentialParameters parameters);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ enum PublicApi {
ACQUIRE_TOKEN_BY_DEVICE_CODE_FLOW(620),
ACQUIRE_TOKEN_FOR_CLIENT(729),
ACQUIRE_TOKEN_BY_AUTHORIZATION_CODE(831),
ACQUIRE_TOKEN_BY_USER_FEDERATED_IDENTITY_CREDENTIAL(900),
ACQUIRE_TOKEN_SILENTLY(800),
GET_ACCOUNTS(801),
REMOVE_ACCOUNTS(802),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.microsoft.aad.msal4j;

import java.util.Map;
import java.util.Set;
import java.util.UUID;

import static com.microsoft.aad.msal4j.ParameterValidationUtils.validateNotBlank;
import static com.microsoft.aad.msal4j.ParameterValidationUtils.validateNotNull;

/**
* Object containing parameters for the User Federated Identity Credential (user_fic) flow.
* This is used for Leg 3 of the agent identity protocol, where a federated identity credential
* (obtained from Leg 2) is exchanged for a user-scoped token.
* <p>
* Can be used as parameter to
* {@link ConfidentialClientApplication#acquireToken(UserFederatedIdentityCredentialParameters)}
*/
public class UserFederatedIdentityCredentialParameters implements IAcquireTokenParameters {

private Set<String> scopes;
private String username;
private UUID userObjectId;
private String assertion;
private boolean forceRefresh;
private ClaimsRequest claims;
private Map<String, String> extraHttpHeaders;
private Map<String, String> extraQueryParameters;
private String tenant;

private UserFederatedIdentityCredentialParameters(
Set<String> scopes,
String username,
UUID userObjectId,
String assertion,
boolean forceRefresh,
ClaimsRequest claims,
Map<String, String> extraHttpHeaders,
Map<String, String> extraQueryParameters,
String tenant) {
this.scopes = scopes;
this.username = username;
this.userObjectId = userObjectId;
this.assertion = assertion;
this.forceRefresh = forceRefresh;
this.claims = claims;
this.extraHttpHeaders = extraHttpHeaders;
this.extraQueryParameters = extraQueryParameters;
this.tenant = tenant;
}

/**
* Builder for {@link UserFederatedIdentityCredentialParameters} using a UPN (User Principal Name).
*
* @param scopes scopes application is requesting access to
* @param username the UPN of the target user (e.g., "[email protected]")
* @param assertion the federated identity credential assertion (JWT) obtained from Leg 2
* @return builder that can be used to construct UserFederatedIdentityCredentialParameters
*/
public static UserFederatedIdentityCredentialParametersBuilder builder(
Set<String> scopes, String username, String assertion) {
validateNotNull("scopes", scopes);
validateNotBlank("username", username);
validateNotBlank("assertion", assertion);

return new UserFederatedIdentityCredentialParametersBuilder()
.scopes(scopes)
.username(username)
.assertion(assertion);
}

/**
* Builder for {@link UserFederatedIdentityCredentialParameters} using a user Object ID.
*
* @param scopes scopes application is requesting access to
* @param userObjectId the Object ID (OID) of the target user
* @param assertion the federated identity credential assertion (JWT) obtained from Leg 2
* @return builder that can be used to construct UserFederatedIdentityCredentialParameters
*/
public static UserFederatedIdentityCredentialParametersBuilder builder(
Set<String> scopes, UUID userObjectId, String assertion) {
validateNotNull("scopes", scopes);
validateNotNull("userObjectId", userObjectId);
validateNotBlank("assertion", assertion);

return new UserFederatedIdentityCredentialParametersBuilder()
.scopes(scopes)
.userObjectId(userObjectId)
.assertion(assertion);
}

public Set<String> scopes() {
return this.scopes;
}

/**
* @return the UPN of the target user, or null if user was identified by Object ID
*/
public String username() {
return this.username;
}

/**
* @return the Object ID of the target user, or null if user was identified by UPN
*/
public UUID userObjectId() {
return this.userObjectId;
}

/**
* @return the federated identity credential assertion (JWT)
*/
public String assertion() {
return this.assertion;
}

/**
* @return whether to bypass the token cache and force a fresh token request
*/
public boolean forceRefresh() {
return this.forceRefresh;
}

public ClaimsRequest claims() {
return this.claims;
}

public Map<String, String> extraHttpHeaders() {
return this.extraHttpHeaders;
}

public Map<String, String> extraQueryParameters() {
return this.extraQueryParameters;
}

public String tenant() {
return this.tenant;
}

public static class UserFederatedIdentityCredentialParametersBuilder {
private Set<String> scopes;
private String username;
private UUID userObjectId;
private String assertion;
private boolean forceRefresh;
private ClaimsRequest claims;
private Map<String, String> extraHttpHeaders;
private Map<String, String> extraQueryParameters;
private String tenant;

UserFederatedIdentityCredentialParametersBuilder() {
}

UserFederatedIdentityCredentialParametersBuilder scopes(Set<String> scopes) {
this.scopes = scopes;
return this;
}

UserFederatedIdentityCredentialParametersBuilder username(String username) {
this.username = username;
return this;
}

UserFederatedIdentityCredentialParametersBuilder userObjectId(UUID userObjectId) {
this.userObjectId = userObjectId;
return this;
}

UserFederatedIdentityCredentialParametersBuilder assertion(String assertion) {
this.assertion = assertion;
return this;
}

/**
* Forces MSAL to refresh the token from the identity provider even if a cached token is available.
*
* @param forceRefresh true to bypass the cache; otherwise false. Default is false.
* @return the builder
*/
public UserFederatedIdentityCredentialParametersBuilder forceRefresh(boolean forceRefresh) {
this.forceRefresh = forceRefresh;
return this;
}

/**
* Claims to be requested through the OIDC claims request parameter.
*/
public UserFederatedIdentityCredentialParametersBuilder claims(ClaimsRequest claims) {
this.claims = claims;
return this;
}

/**
* Adds additional headers to the token request.
*/
public UserFederatedIdentityCredentialParametersBuilder extraHttpHeaders(Map<String, String> extraHttpHeaders) {
this.extraHttpHeaders = extraHttpHeaders;
return this;
}

/**
* Adds additional parameters to the token request.
*/
public UserFederatedIdentityCredentialParametersBuilder extraQueryParameters(Map<String, String> extraQueryParameters) {
this.extraQueryParameters = extraQueryParameters;
return this;
}

/**
* Overrides the tenant value in the authority URL for this request.
*/
public UserFederatedIdentityCredentialParametersBuilder tenant(String tenant) {
this.tenant = tenant;
return this;
}

public UserFederatedIdentityCredentialParameters build() {
return new UserFederatedIdentityCredentialParameters(
this.scopes, this.username, this.userObjectId, this.assertion,
this.forceRefresh, this.claims, this.extraHttpHeaders,
this.extraQueryParameters, this.tenant);
}
}
}
Loading