Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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
11 changes: 8 additions & 3 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
- **Main Package**: `com.microsoft.aad.msal4j`
- **Three Application Types**: `PublicClientApplication`, `ConfidentialClientApplication`, `ManagedIdentityApplication`
- **Pattern**: Each auth flow has `*Parameters` (public API), `*Request` (internal), `*Supplier` (executor)
- **Current Version**: 1.26.0
- **Current Version**: 1.27.0-SNAPSHOT

---

Expand All @@ -16,7 +16,7 @@

- **Language**: Java 8+
- **Build Tool**: Maven
- **Current Version**: 1.26.0
- **Current Version**: 1.27.0-SNAPSHOT
- **Artifact**: `com.microsoft.azure:msal4j`
- **Key Protocols**: OAuth2, OpenID Connect

Expand All @@ -28,10 +28,12 @@
- Multi-cloud and B2C support

### Repository Structure
This repository contains three Maven modules:
This repository contains four default Maven modules plus one profile-only E2E module:
- **`msal4j-sdk/`** - The main MSAL Java library (focus of development)
- **`msal4j-brokers/`** - Broker integration for native authentication (Windows WAM)
- **`msal4j-persistence-extension/`** - Cross-platform token cache persistence helpers
- **`msal4j-mtls-extensions/`** - Optional Windows KeyGuard/attestation bridge for Managed Identity v2 mTLS PoP; bundles Microsoft.Azure.Security.KeyGuardAttestation 1.1.5 while Java JCA/JSSE performs TLS
- **`msal4j-mtls-extensions-e2e/`** - Managed Identity v1 and v2 validation app, included only by the Maven `e2e` profile and used by the VM-based PR pipeline jobs

For most work, focus on **`msal4j-sdk/`**.

Expand Down Expand Up @@ -151,6 +153,9 @@ MSAL4J supports multiple authentication flows, each with a public `*Parameters`
- **Parameters**: `ManagedIdentityParameters` - For Azure resources (VMs, App Service, Functions)
- **Internal**: `ManagedIdentityRequest` → `AcquireTokenByManagedIdentitySupplier`
- **Key Classes**: `ManagedIdentitySource` implementations (`IMDSManagedIdentitySource`, `AppServiceManagedIdentitySource`, etc.)
- **Optional mTLS**: `withMtlsProofOfPossession()` requests a certificate-bound token; `withRequestOverMtls()` requests an ordinary bearer token over the same KeyGuard-authenticated ESTS channel. The optional `msal4j-key-attestation` artifact supplies `ManagedIdentityAttestationExtensions.withAttestationSupport(builder)` to require MAA attestation and fail closed. Core owns OAuth/HTTP/cache behavior; the optional artifact owns the public attestation opt-in plus KeyGuard/CNG/attestation and returns a reusable process-local `IMtlsBindingContext` for PoP results. Custom HTTP clients must implement `IMtlsCapableHttpClient` and consume the request-specific context or socket factory.
- **mTLS capability discovery**: `getManagedIdentityCapabilities()` reports the detected source and maximum `MtlsBindingStrength`; `MtlsPopOptions` lets credential chains require a minimum strength before token acquisition succeeds.
- **mTLS recovery**: A qualifying `invalid_client` response or TLS failure invalidates the current provider binding, remints it, and retries the token leg once. Providers opt into this behavior through `invalidateBinding`; retries are bounded.

### Common Flows (All Application Types)

Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,20 @@ MSAL4J supports multiple [application types and authentication scenarios](https:

Refer the [Wiki](https://github.com/AzureAD/microsoft-authentication-library-for-java/wiki) pages for more details on the usage of MSAL Java and the supported scenarios.

### Managed Identity v2 attested mTLS PoP

The optional `com.microsoft.azure:msal4j-key-attestation` artifact provides a
Windows KeyGuard-backed, attested Managed Identity v2 flow while preserving
Java 8 compatibility. MSAL returns an `IMtlsBindingContext` whose standard JSSE
`SSLContext` can be used by the application for independent downstream mTLS
calls.

See [the extension guide](msal4j-mtls-extensions/README.md). Reviewers can use
the [detailed review guide](msal4j-sdk/docs/managed-identity-v2-mtls-pop-review-guide.md)
for architecture diagrams, security checklists, file order, and manual validation.
The [DevEx guide](msal4j-sdk/docs/managed-identity-v2-devex.md) shows the
before/after API and the two supported mTLS request modes.

## Migrating from ADAL
If your application is using ADAL for Java (ADAL4J), we recommend you to update to use MSAL4J. No new feature work will be done in ADAL4J.

Expand Down
318 changes: 206 additions & 112 deletions build/azure-pr-pipeline.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,115 +21,209 @@ variables:
skip.integration.tests: false
system.debug: false

pool:
vmImage: windows-2025

steps:
- checkout: self
clean: false
fetchDepth: 0
fetchTags: false

- task: DownloadSecureFile@1
name: LabAuth
displayName: Download LabAuth certificate
inputs:
secureFile: 6cfea379-ebe1-4e95-b2b8-1b509a4e70c4
retryCount: "8"

- task: PowerShell@2
displayName: Save certificate in store
inputs:
targetType: inline
script: |
$ErrorActionPreference = "Stop"

Import-PfxCertificate `
-FilePath "$(LabAuth.secureFilePath)" `
-CertStoreLocation "Cert:\CurrentUser\My" `
-Exportable

- task: PowerShell@2
displayName: Install Chrome and ChromeDriver
inputs:
targetType: inline
script: |
$ErrorActionPreference = "Stop"

$installer = Join-Path $env:Agent_TempDirectory "chrome-installer.exe"
Invoke-WebRequest `
-Uri "https://dl.google.com/chrome/install/latest/chrome_installer.exe" `
-OutFile $installer

Start-Process `
-FilePath $installer `
-ArgumentList "/silent", "/install" `
-Wait

$chromePath = "${env:ProgramFiles}\Google\Chrome\Application\chrome.exe"
$chromeVersion = (Get-Item $chromePath).VersionInfo.ProductVersion
$chromeBuild = ($chromeVersion.Split(".")[0..2] -join ".")

Write-Host "Installed Chrome version: $chromeVersion"

$driverVersion = Invoke-RestMethod `
"https://googlechromelabs.github.io/chrome-for-testing/LATEST_RELEASE_$chromeBuild"

Write-Host "Using ChromeDriver version: $driverVersion"

$zip = Join-Path $env:Agent_TempDirectory "chromedriver.zip"
$extract = Join-Path $env:Agent_TempDirectory "chromedriver"

Invoke-WebRequest `
-Uri "https://storage.googleapis.com/chrome-for-testing-public/$driverVersion/win64/chromedriver-win64.zip" `
-OutFile $zip

Expand-Archive -Path $zip -DestinationPath $extract -Force
Copy-Item `
"$extract\chromedriver-win64\chromedriver.exe" `
"C:\Windows\chromedriver.exe" `
-Force

- task: Maven@4
displayName: Run unit tests
inputs:
mavenPomFile: pom.xml
goals: verify
options: >-
-fae
-Drevapi.failBuildOnProblemsFound=false
-Dskip.unit.tests=$(skip.unit.tests)
-Dskip.integration.tests=true
-Dadfs.disabled=true
publishJUnitResults: true
testResultsFiles: "**/surefire-reports/TEST-*.xml"
javaHomeOption: JDKVersion
jdkVersionOption: "1.8"
jdkArchitectureOption: x64

- task: Maven@4
displayName: Run integration tests
inputs:
mavenPomFile: pom.xml
goals: verify
options: >-
-fae
-Drevapi.failBuildOnProblemsFound=false
-Dskip.unit.tests=true
-Dskip.integration.tests=$(skip.integration.tests)
-Dadfs.disabled=true
publishJUnitResults: true
testResultsFiles: "**/failsafe-reports/TEST-*.xml"
javaHomeOption: JDKVersion
jdkVersionOption: "1.8"
jdkArchitectureOption: x64

- task: PublishCodeCoverageResults@2
displayName: Publish JaCoCo coverage
condition: succeededOrFailed()
inputs:
summaryFileLocation: >-
$(System.DefaultWorkingDirectory)\msal4j-sdk\target\site\jacoco\jacoco.xml
pathToSources: >-
$(System.DefaultWorkingDirectory)\msal4j-sdk\src\main\java
failIfCoverageEmpty: false
jobs:
- job: BuildAndTest
displayName: Build and test
pool:
vmImage: windows-2025
steps:
- checkout: self
clean: false
fetchDepth: 0
fetchTags: false

- task: DownloadSecureFile@1
name: LabAuth
displayName: Download LabAuth certificate
inputs:
secureFile: 6cfea379-ebe1-4e95-b2b8-1b509a4e70c4
retryCount: "8"

- task: PowerShell@2
displayName: Save certificate in store
inputs:
targetType: inline
script: |
$ErrorActionPreference = "Stop"

Import-PfxCertificate `
-FilePath "$(LabAuth.secureFilePath)" `
-CertStoreLocation "Cert:\CurrentUser\My" `
-Exportable

- task: PowerShell@2
displayName: Install Chrome and ChromeDriver
inputs:
targetType: inline
script: |
$ErrorActionPreference = "Stop"

$installer = Join-Path $env:Agent_TempDirectory "chrome-installer.exe"
Invoke-WebRequest `
-Uri "https://dl.google.com/chrome/install/latest/chrome_installer.exe" `
-OutFile $installer

Start-Process `
-FilePath $installer `
-ArgumentList "/silent", "/install" `
-Wait

$chromePath = "${env:ProgramFiles}\Google\Chrome\Application\chrome.exe"
$chromeVersion = (Get-Item $chromePath).VersionInfo.ProductVersion
$chromeBuild = ($chromeVersion.Split(".")[0..2] -join ".")

Write-Host "Installed Chrome version: $chromeVersion"

$driverVersion = Invoke-RestMethod `
"https://googlechromelabs.github.io/chrome-for-testing/LATEST_RELEASE_$chromeBuild"

Write-Host "Using ChromeDriver version: $driverVersion"

$zip = Join-Path $env:Agent_TempDirectory "chromedriver.zip"
$extract = Join-Path $env:Agent_TempDirectory "chromedriver"

Invoke-WebRequest `
-Uri "https://storage.googleapis.com/chrome-for-testing-public/$driverVersion/win64/chromedriver-win64.zip" `
-OutFile $zip

Expand-Archive -Path $zip -DestinationPath $extract -Force
Copy-Item `
"$extract\chromedriver-win64\chromedriver.exe" `
"C:\Windows\chromedriver.exe" `
-Force

- task: Maven@4
displayName: Run unit tests
inputs:
mavenPomFile: pom.xml
goals: verify
options: >-
-fae
-Drevapi.failBuildOnProblemsFound=false
-Dskip.unit.tests=$(skip.unit.tests)
-Dskip.integration.tests=true
-Dadfs.disabled=true
publishJUnitResults: true
testResultsFiles: "**/surefire-reports/TEST-*.xml"
javaHomeOption: JDKVersion
jdkVersionOption: "1.8"
jdkArchitectureOption: x64

- task: Maven@4
displayName: Run integration tests
inputs:
mavenPomFile: pom.xml
goals: verify
options: >-
-fae
-Drevapi.failBuildOnProblemsFound=false
-Dskip.unit.tests=true
-Dskip.integration.tests=$(skip.integration.tests)
-Dadfs.disabled=true
publishJUnitResults: true
testResultsFiles: "**/failsafe-reports/TEST-*.xml"
javaHomeOption: JDKVersion
jdkVersionOption: "1.8"
jdkArchitectureOption: x64

- task: PublishCodeCoverageResults@2
displayName: Publish JaCoCo coverage
condition: succeededOrFailed()
inputs:
summaryFileLocation: >-
$(System.DefaultWorkingDirectory)\msal4j-sdk\target\site\jacoco\jacoco.xml
pathToSources: >-
$(System.DefaultWorkingDirectory)\msal4j-sdk\src\main\java
failIfCoverageEmpty: false

- job: ManagedIdentityV1
displayName: Managed Identity E2E - IMDS v1
pool:
name: MISEManagedIdentity
variables:
runCodesignValidationInjection: false
Codeql.SkipTaskAutoInjection: true
steps:
- checkout: self
clean: true
fetchDepth: 1

- task: PowerShell@2
displayName: Build Managed Identity E2E app
inputs:
filePath: build/build-managed-identity-e2e.ps1

- task: PowerShell@2
displayName: Run Managed Identity v1 E2E
env:
MSAL_JAVA_MI_E2E_MODE: v1
inputs:
targetType: inline
script: |
$ErrorActionPreference = "Stop"
$jar = Get-ChildItem `
"$(System.DefaultWorkingDirectory)\msal4j-mtls-extensions-e2e\target" `
-Filter "*-e2e.jar" |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
if ($null -eq $jar) {
throw "Managed Identity E2E JAR was not produced."
}
& java -jar $jar.FullName
if ($LASTEXITCODE -ne 0) {
throw "Managed Identity v1 E2E failed with exit code $LASTEXITCODE."
}

- job: ManagedIdentityV2
displayName: Managed Identity E2E - IMDS v2 mTLS PoP
pool:
name: MISEManagedIdentity
variables:
runCodesignValidationInjection: false
Codeql.SkipTaskAutoInjection: true
steps:
- checkout: self
clean: true
fetchDepth: 1

- task: PowerShell@2
displayName: Build Managed Identity E2E app
inputs:
filePath: build/build-managed-identity-e2e.ps1

- task: PowerShell@2
displayName: Verify KeyGuard prerequisites
inputs:
targetType: inline
script: |
$ErrorActionPreference = "Stop"
$tpm = Get-Tpm
if (-not $tpm.TpmPresent -or -not $tpm.TpmReady) {
throw "A present and ready TPM is required."
}
if (-not (Confirm-SecureBootUEFI)) {
throw "Secure Boot is not enabled."
}

- task: PowerShell@2
displayName: Run Managed Identity v2 mTLS PoP E2E
env:
MSAL_JAVA_MI_E2E_MODE: v2
MSAL_JAVA_MTLS_AKV_URL: https://tokenbinding.vault.azure.net
MSAL_JAVA_MTLS_AKV_SECRET_NAME: boundsecret
inputs:
targetType: inline
script: |
$ErrorActionPreference = "Stop"
$jar = Get-ChildItem `
"$(System.DefaultWorkingDirectory)\msal4j-mtls-extensions-e2e\target" `
-Filter "*-e2e.jar" |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
if ($null -eq $jar) {
throw "Managed Identity E2E JAR was not produced."
}
& java -jar $jar.FullName
if ($LASTEXITCODE -ne 0) {
throw "Managed Identity v2 E2E failed with exit code $LASTEXITCODE."
}
Loading
Loading