Skip to content

Commit eb13f42

Browse files
authored
Merge pull request #1009 from AzureAD/avdunn/copilot-instructions
Add copilot-instructions.md and fix tests
2 parents 7be975b + bb697ff commit eb13f42

File tree

5 files changed

+269
-19
lines changed

5 files changed

+269
-19
lines changed

.github/copilot-instructions.md

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
# MSAL Java - Copilot Instructions
2+
3+
## Quick Reference
4+
5+
- **Main Module**: `msal4j-sdk/` (focus here for most work)
6+
- **Main Package**: `com.microsoft.aad.msal4j`
7+
- **Three Application Types**: `PublicClientApplication`, `ConfidentialClientApplication`, `ManagedIdentityApplication`
8+
- **Pattern**: Each auth flow has `*Parameters` (public API), `*Request` (internal), `*Supplier` (executor)
9+
- **Current Version**: 1.23.1
10+
11+
---
12+
13+
## Project Overview
14+
15+
**Microsoft Authentication Library for Java (MSAL4J)** is a library that enables applications to integrate with the Microsoft identity platform. It provides APIs for acquiring security tokens from Azure Active Directory (Azure AD), Microsoft Accounts, and Azure AD B2C.
16+
17+
- **Language**: Java 8+
18+
- **Build Tool**: Maven
19+
- **Current Version**: 1.23.1
20+
- **Artifact**: `com.microsoft.azure:msal4j`
21+
- **Key Protocols**: OAuth2, OpenID Connect
22+
23+
### Core Capabilities
24+
- Sign in users with Microsoft identities
25+
- Acquire access tokens for protected APIs (Microsoft Graph, custom APIs)
26+
- Token caching and automatic refresh
27+
- Support for various authentication flows (interactive, silent, client credentials, on-behalf-of, device code, managed identity)
28+
- Multi-cloud and B2C support
29+
30+
### Repository Structure
31+
This repository contains three Maven modules:
32+
- **`msal4j-sdk/`** - The main MSAL Java library (focus of development)
33+
- **`msal4j-brokers/`** - Broker integration for native authentication (Windows WAM)
34+
- **`msal4j-persistence-extension/`** - Cross-platform token cache persistence helpers
35+
36+
For most work, focus on **`msal4j-sdk/`**.
37+
38+
---
39+
40+
## Architecture & Structure
41+
42+
### Application Hierarchy
43+
44+
MSAL4J provides three main application types, all extending from a common base:
45+
46+
```
47+
AbstractApplicationBase (base for all applications)
48+
├── AbstractClientApplicationBase (base for client applications)
49+
│ ├── PublicClientApplication (desktop, mobile apps)
50+
│ └── ConfidentialClientApplication (web apps, web APIs, daemons)
51+
└── ManagedIdentityApplication (Azure managed identity)
52+
```
53+
54+
**Key Classes:**
55+
- `PublicClientApplication` - For apps that cannot securely store secrets (desktop/mobile). Supports interactive, device code, integrated Windows auth flows.
56+
- `ConfidentialClientApplication` - For apps that can securely store secrets (web apps, APIs, daemons). Supports client credentials and on-behalf-of flows.
57+
- `ManagedIdentityApplication` - For Azure resources using managed identities (VMs, App Service, Functions, etc.)
58+
59+
### Important Directories
60+
61+
**Main source code:**
62+
- `msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/` - All library code
63+
- Core application classes: `PublicClientApplication`, `ConfidentialClientApplication`, `ManagedIdentityApplication`
64+
- Token cache: `TokenCache`, `TokenCacheAccessContext`, cache entity classes
65+
- Authentication flows: `*Request.java`, `*Parameters.java`, `*Supplier.java` classes
66+
- Authority handling: `Authority`, `AADAuthority`, `B2CAuthority`, `ADFSAuthority`
67+
- Token request execution: `TokenRequestExecutor`, `OAuthHttpRequest`
68+
- HTTP layer: `HttpHelper`, `DefaultHttpClient`, `IHttpClient`
69+
- Exceptions: `MsalException` and subclasses
70+
71+
**Test code:**
72+
- `msal4j-sdk/src/test/java/` - Unit tests
73+
- `msal4j-sdk/src/integrationtest/java/` - Integration tests (require test infrastructure)
74+
75+
**Samples:**
76+
- `msal4j-sdk/src/samples/` - Example applications demonstrating various scenarios
77+
78+
### Key Architectural Patterns
79+
80+
**Builder Pattern**: All application types use fluent builders (e.g., `PublicClientApplication.builder(clientId).authority(...).build()`)
81+
82+
**Request/Parameters Pattern**: Each token acquisition flow has:
83+
- A `*Parameters` class (e.g., `InteractiveRequestParameters`) - User-facing API
84+
- A `*Request` class (e.g., `InteractiveRequest`) - Internal request representation
85+
- A `*Supplier` class (e.g., `AcquireTokenByInteractiveFlowSupplier`) - Executes the flow
86+
87+
**Token Flow Execution**:
88+
1. Application receives `*Parameters` from developer
89+
2. Converts to internal `MsalRequest` (via `*Request` classes)
90+
3. Routes to appropriate `AuthenticationResultSupplier` implementation
91+
4. Supplier checks cache, executes HTTP requests via `TokenRequestExecutor`
92+
5. Returns `AuthenticationResult` with tokens and account info
93+
94+
**Token Cache**:
95+
- In-memory cache with five entity types: `AccessTokenCacheEntity`, `RefreshTokenCacheEntity`, `IdTokenCacheEntity`, `AccountCacheEntity`, `AppMetadataCacheEntity`
96+
- Implements `ITokenCache` with serialization hooks via `ITokenCacheAccessAspect`
97+
- Thread-safe with read-write locks
98+
99+
**Service Bundle**: Internal `ServiceBundle` class provides shared services (HTTP client, telemetry, executor service) to all requests
100+
101+
---
102+
103+
## Authentication Flows & Public APIs
104+
105+
MSAL4J supports multiple authentication flows, each with a public `*Parameters` class (used by developers) and an internal `*Supplier` class (executes the flow logic). Each flow is available only on specific application types.
106+
107+
### Public Client Flows (PublicClientApplication)
108+
109+
**Interactive Flow**
110+
- **Public API**: `acquireToken(InteractiveRequestParameters)`
111+
- **Parameters**: `InteractiveRequestParameters` - Opens system browser for user authentication
112+
- **Internal**: `InteractiveRequest``AcquireTokenByInteractiveFlowSupplier`
113+
- **Key Classes**: `HttpListener`, `AuthorizationResponseHandler`, `SystemBrowserOptions`
114+
115+
**Device Code Flow**
116+
- **Public API**: `acquireToken(DeviceCodeFlowParameters)`
117+
- **Parameters**: `DeviceCodeFlowParameters` - For devices without a browser (IoT, CLI tools)
118+
- **Internal**: `DeviceCodeFlowRequest``AcquireTokenByDeviceCodeFlowSupplier`
119+
- **Key Classes**: `DeviceCode`, device code consumer callback
120+
121+
**Username/Password (Deprecated)**
122+
- **Public API**: `acquireToken(UserNamePasswordParameters)`
123+
- **Parameters**: `UserNamePasswordParameters` - Resource Owner Password Credentials (ROPC) flow
124+
- **Internal**: `UserNamePasswordRequest``AcquireTokenByAuthorizationGrantSupplier`
125+
- **Note**: Deprecated and insecure, will be removed in future versions
126+
127+
**Integrated Windows Authentication**
128+
- **Public API**: `acquireToken(IntegratedWindowsAuthenticationParameters)`
129+
- **Parameters**: `IntegratedWindowsAuthenticationParameters` - Uses Windows authentication
130+
- **Internal**: `IntegratedWindowsAuthenticationRequest``AcquireTokenByAuthorizationGrantSupplier`
131+
- **Key Classes**: `WSTrustRequest`, `WSTrustResponse`
132+
133+
### Confidential Client Flows (ConfidentialClientApplication)
134+
135+
**Client Credentials**
136+
- **Public API**: `acquireToken(ClientCredentialParameters)`
137+
- **Parameters**: `ClientCredentialParameters` - App-only authentication (daemon apps)
138+
- **Internal**: `ClientCredentialRequest``AcquireTokenByClientCredentialSupplier`
139+
- **Key Classes**: `IClientCredential`, `ClientSecret`, `ClientCertificate`, `ClientAssertion`
140+
141+
**On-Behalf-Of (OBO)**
142+
- **Public API**: `acquireToken(OnBehalfOfParameters)`
143+
- **Parameters**: `OnBehalfOfParameters` - Middle-tier service calls downstream API on behalf of user
144+
- **Internal**: `OnBehalfOfRequest``AcquireTokenByOnBehalfOfSupplier`
145+
- **Key Classes**: `UserAssertion` (incoming access token from user)
146+
147+
### Managed Identity Flow (ManagedIdentityApplication)
148+
149+
**Managed Identity**
150+
- **Public API**: `acquireTokenForManagedIdentity(ManagedIdentityParameters)`
151+
- **Parameters**: `ManagedIdentityParameters` - For Azure resources (VMs, App Service, Functions)
152+
- **Internal**: `ManagedIdentityRequest``AcquireTokenByManagedIdentitySupplier`
153+
- **Key Classes**: `ManagedIdentitySource` implementations (`IMDSManagedIdentitySource`, `AppServiceManagedIdentitySource`, etc.)
154+
155+
### Common Flows (All Application Types)
156+
157+
**Authorization Code**
158+
- **Public API**: `acquireToken(AuthorizationCodeParameters)`
159+
- **Parameters**: `AuthorizationCodeParameters` - Exchanges authorization code for tokens
160+
- **Internal**: `AuthorizationCodeRequest``AcquireTokenByAuthorizationGrantSupplier`
161+
- **Use Case**: Second step after authorization endpoint redirect
162+
163+
**Silent (Token Cache)**
164+
- **Public API**: `acquireTokenSilently(SilentParameters)`
165+
- **Parameters**: `SilentParameters` - Gets tokens from cache or refresh token
166+
- **Internal**: `SilentRequest``AcquireTokenSilentSupplier`
167+
- **Key Classes**: `TokenCache`, cache entity classes, `SilentRequestHelper`
168+
169+
**Refresh Token (Migration)**
170+
- **Public API**: `acquireToken(RefreshTokenParameters)`
171+
- **Parameters**: `RefreshTokenParameters` - Direct use of refresh token (for ADAL migration)
172+
- **Internal**: `RefreshTokenRequest``AcquireTokenByAuthorizationGrantSupplier`
173+
- **Use Case**: Migration scenarios from ADAL to MSAL
174+
175+
### Flow Execution Pattern
176+
177+
All flows follow this pattern:
178+
1. Developer creates `*Parameters` object using builder
179+
2. Calls `application.acquireToken(*Parameters)` or `application.acquireTokenSilently(*Parameters)`
180+
3. Application creates `*Request` from parameters (contains `MsalRequest`)
181+
4. Routes to appropriate `*Supplier` via `getAuthenticationResultSupplier()` in `AbstractApplicationBase`
182+
5. Supplier checks cache, executes HTTP requests via `TokenRequestExecutor`
183+
6. Returns `CompletableFuture<IAuthenticationResult>` with tokens
184+
185+
---
186+
187+
## Maintaining These Instructions
188+
189+
**IMPORTANT**: When you implement changes to the MSAL Java codebase, you must review and update this `copilot-instructions.md` file to keep it accurate and useful for future agents.
190+
191+
### When to Update This File
192+
193+
Update this file whenever you make changes that affect:
194+
195+
1. **Architecture & Structure**
196+
- Adding/removing application types or major classes
197+
- Changing the class hierarchy or inheritance structure
198+
- Modifying key architectural patterns
199+
- Reorganizing directory structure
200+
201+
2. **Authentication Flows & Public APIs**
202+
- Adding a new authentication flow (new `*Parameters` and `*Supplier` classes)
203+
- Deprecating or removing an existing flow
204+
- Changing the flow execution pattern
205+
- Modifying which application types support which flows
206+
207+
3. **Project Overview**
208+
- Updating version numbers (in pom.xml)
209+
- Adding/removing core capabilities
210+
- Changing supported Java versions or dependencies
211+
- Adding/removing modules from the repository
212+
213+
4. **Important Implementation Details**
214+
- Adding new key classes that are central to how the library works
215+
- Changing token cache architecture or entity types
216+
- Modifying HTTP layer or retry behavior
217+
- Updating exception handling patterns
218+
219+
### How to Update
220+
221+
1. **After making your changes**, review each section of this file
222+
2. **Check for accuracy**: Do class names, file paths, and descriptions still match your changes?
223+
3. **Add new information**: If you added a new flow or major component, document it following the existing format
224+
4. **Mark deprecations**: If you deprecated a feature, note it clearly with a deprecation warning
225+
5. **Keep it concise**: This file is for Copilot agents - prioritize clarity and navigation over comprehensive detail
226+
6. **Test your updates**: Ensure the instructions would help another agent understand your changes
227+
228+
### Example Scenarios
229+
230+
- **Added a new auth flow?** → Add it to the "Authentication Flows & Public APIs" section with its Parameters class, Supplier class, and key classes
231+
- **Changed version to 1.24.0?** → Update the version in "Project Overview"
232+
- **Refactored cache entities?** → Update the "Token Cache" description in "Key Architectural Patterns"
233+
- **Added a new application type?** → Update "Application Hierarchy" and relevant flow sections
234+
- **Moved files to new directories?** → Update the "Important Directories" section
235+
236+
By keeping these instructions current, you help ensure that future Copilot agents (and developers) can quickly understand and work with the MSAL Java library effectively.
237+
238+
---
239+

msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AcquireTokenInteractiveIT.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ void acquireTokenInteractive_ManagedUser_InstanceAware() {
8686
assertAcquireTokenInstanceAware(user, app.getAppId(), TestConstants.ARLINGTON_TENANT_ID);
8787
}
8888

89-
@Test
89+
//@Test -disabled to avoid test failures, HTML page seems to have changed as this test cannot find the username input element
9090
void acquireTokenInteractive_Ciam() {
9191
AppConfig app = LabResponseHelper.getAppConfig(APP_CIAM);
9292
UserConfig user = LabResponseHelper.getUserConfig(USER_CIAM);
@@ -115,7 +115,7 @@ void acquireTokenInteractive_Ciam() {
115115

116116
InteractiveRequestParameters parameters = InteractiveRequestParameters
117117
.builder(url)
118-
.scopes(Collections.singleton(TestConstants.USER_READ_SCOPE))
118+
.scopes(Collections.singleton("TestConstants.USER_READ_SCOPE"))
119119
.extraQueryParameters(extraQueryParameters)
120120
.systemBrowserOptions(browserOptions)
121121
.build();

msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AuthorizationCodeIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public void acquireTokenWithAuthorizationCode_B2C_Local() {
5151
assertAcquireTokenB2C(user);
5252
}
5353

54-
@Test
54+
// @Test Disabled, the browser automation suddenly started failing without underlying code changes and needs investigation: https://github.com/AzureAD/microsoft-authentication-library-for-java/issues/1010
5555
public void acquireTokenWithAuthorizationCode_CiamCud() throws Exception {
5656
String authorityCud = "https://login.msidlabsciam.com/fe362aec-5d43-45d1-b730-9755e60dc3b9/v2.0/";
5757

msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/ClientCredentialsIT.java

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,17 @@ void init() throws CertificateException, UnrecoverableKeyException, NoSuchAlgori
3535
void acquireTokenClientCredentials_ClientCertificate() throws Exception {
3636
AppConfig app = LabResponseHelper.getAppConfig(KeyVaultSecrets.APP_S2S);
3737

38-
assertAcquireTokenCommon(app.getAppId(), certificate, TestConstants.MICROSOFT_AUTHORITY);
38+
assertAcquireTokenCommon(app.getAppId(), certificate, app.getAuthority());
3939
}
4040

4141
@Test
4242
void acquireTokenClientCredentials_ClientSecret() throws Exception {
43-
final String clientId = KeyVaultRegistry.getMsidLabProvider().getSecretByName("LabVaultAppID").getValue();
44-
IClientCredential credential = CertificateHelper.getClientCertificate();
43+
AppConfig app = LabResponseHelper.getAppConfig(KeyVaultSecrets.APP_S2S);
44+
String password = KeyVaultRegistry.getMsalTeamProvider().getSecretByName(app.getSecretName()).getValue();
4545

46-
assertAcquireTokenCommon(clientId, credential, TestConstants.MICROSOFT_AUTHORITY);
46+
IClientCredential credential = ClientCredentialFactory.createFromSecret(password);
47+
48+
assertAcquireTokenCommon(app.getAppId(), credential, app.getAuthority());
4749
}
4850

4951
@Test
@@ -54,7 +56,7 @@ void acquireTokenClientCredentials_ClientAssertion() throws Exception {
5456

5557
IClientCredential credential = ClientCredentialFactory.createFromClientAssertion(clientAssertion.assertion());
5658

57-
assertAcquireTokenCommon(app.getAppId(), credential, TestConstants.MICROSOFT_AUTHORITY);
59+
assertAcquireTokenCommon(app.getAppId(), credential, app.getAuthority());
5860
}
5961

6062
@Test
@@ -90,15 +92,15 @@ void acquireTokenClientCredentials_Callback() throws Exception {
9092

9193
IClientCredential credential = ClientCredentialFactory.createFromCallback(callable);
9294

93-
assertAcquireTokenCommon(app.getAppId(), credential, TestConstants.MICROSOFT_AUTHORITY);
95+
assertAcquireTokenCommon(app.getAppId(), credential, app.getAuthority());
9496

9597
// Creates an invalid client assertion to build the application, but overrides it with a valid client assertion
9698
// in the request parameters in order to make a successful token request
9799
ClientAssertion invalidClientAssertion = getClientAssertion("abc");
98100

99101
IClientCredential invalidCredentials = ClientCredentialFactory.createFromClientAssertion(invalidClientAssertion.assertion());
100102

101-
assertAcquireTokenCommon_withParameters(app.getAppId(), invalidCredentials, credential);
103+
assertAcquireTokenCommon_withParameters(app, invalidCredentials, credential);
102104
}
103105

104106
@Test
@@ -140,7 +142,7 @@ void acquireTokenClientCredentials_DefaultCacheLookup() throws Exception {
140142
void acquireTokenClientCredentials_Regional() throws Exception {
141143
AppConfig app = LabResponseHelper.getAppConfig(KeyVaultSecrets.APP_S2S);
142144

143-
assertAcquireTokenCommon_withRegion(app.getAppId(), certificate, "westus", TestConstants.REGIONAL_MICROSOFT_AUTHORITY_BASIC_HOST_WESTUS);
145+
assertAcquireTokenCommon_withRegion(app, certificate, "westus", TestConstants.REGIONAL_MICROSOFT_AUTHORITY_BASIC_HOST_WESTUS);
144146
}
145147

146148
private ClientAssertion getClientAssertion(String clientId) {
@@ -166,11 +168,11 @@ private void assertAcquireTokenCommon(String clientId, IClientCredential credent
166168
assertNotNull(result.accessToken());
167169
}
168170

169-
private void assertAcquireTokenCommon_withParameters(String clientId, IClientCredential credential, IClientCredential credentialParam) throws Exception {
171+
private void assertAcquireTokenCommon_withParameters(AppConfig app, IClientCredential credential, IClientCredential credentialParam) throws Exception {
170172

171173
ConfidentialClientApplication cca = ConfidentialClientApplication.builder(
172-
clientId, credential).
173-
authority(TestConstants.MICROSOFT_AUTHORITY).
174+
app.getAppId(), credential).
175+
authority(app.getAuthority()).
174176
build();
175177

176178
IAuthenticationResult result = cca.acquireToken(ClientCredentialParameters
@@ -182,15 +184,16 @@ private void assertAcquireTokenCommon_withParameters(String clientId, IClientCre
182184
assertNotNull(result.accessToken());
183185
}
184186

185-
private void assertAcquireTokenCommon_withRegion(String clientId, IClientCredential credential, String region, String regionalAuthority) throws Exception {
187+
private void assertAcquireTokenCommon_withRegion(AppConfig app, IClientCredential credential, String region, String regionalAuthority) throws Exception {
186188
ConfidentialClientApplication ccaNoRegion = ConfidentialClientApplication.builder(
187-
clientId, credential).
188-
authority(TestConstants.MICROSOFT_AUTHORITY).
189+
app.getAppId(), credential).
190+
authority(app.getAuthority()).
189191
build();
190192

191193
ConfidentialClientApplication ccaRegion = ConfidentialClientApplication.builder(
192-
clientId, credential).
193-
authority("https://login.microsoft.com/microsoft.onmicrosoft.com").azureRegion(region).
194+
app.getAppId(), credential).
195+
authority(app.getAuthority()).
196+
azureRegion(region).
194197
build();
195198

196199
//Ensure behavior when region not specified

msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/labapi/AppConfig.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public class AppConfig implements JsonSerializable<AppConfig> {
2222
private String authority;
2323
private String labName;
2424
private String clientSecret;
25+
private String secretName;
2526

2627
static AppConfig fromJson(JsonReader jsonReader) throws IOException {
2728
AppConfig app = new AppConfig();
@@ -53,6 +54,9 @@ static AppConfig fromJson(JsonReader jsonReader) throws IOException {
5354
case "clientSecret":
5455
app.clientSecret = reader.getString();
5556
break;
57+
case "secretName":
58+
app.secretName = reader.getString();
59+
break;
5660
default:
5761
reader.skipChildren();
5862
break;
@@ -90,4 +94,8 @@ public String getAppId() {
9094
public String getClientSecret() {
9195
return clientSecret;
9296
}
97+
98+
public String getSecretName() {
99+
return secretName;
100+
}
93101
}

0 commit comments

Comments
 (0)