Skip to content

feat: eSTS cloud host detection and PRT header re-attachment on WebView redirects, Fixes AB#3568709#3071

Open
Copilot wants to merge 14 commits intodevfrom
copilot/ab-3568709-implement-ests-cloud-host-detection
Open

feat: eSTS cloud host detection and PRT header re-attachment on WebView redirects, Fixes AB#3568709#3071
Copilot wants to merge 14 commits intodevfrom
copilot/ab-3568709-implement-ests-cloud-host-detection

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 7, 2026

  • Analyze all handleUrl() branches that can match eSTS https:// URLs before isEstsCloudHost
  • Update NonceRedirectHandler to accept optional fallbackUsername parameter
  • Pass mLoginHint as fallback to NonceRedirectHandler in processNonceAndReAttachHeaders
  • Add NonceRedirectHandlerTest covering fallback username scenarios
  • Fix compile errors: add @JvmOverloads to ReAttachPrtHeaderHandler and NonceRedirectHandler constructors
  • Assert headers[PRT_RESPONSE_HEADER] equals the fresh credential in NonceRedirectHandlerTest success-path test

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 7, 2026

✅ Work item link check complete. Description contains link AB#3568709 to an Azure Boards work item.

1 similar comment
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 7, 2026

✅ Work item link check complete. Description contains link AB#3568709 to an Azure Boards work item.

@github-actions github-actions bot changed the title [WIP] Implement eSTS cloud host detection and PRT header re-attachment [WIP] Implement eSTS cloud host detection and PRT header re-attachment, Fixes AB#3568709 Apr 7, 2026
Copilot AI changed the title [WIP] Implement eSTS cloud host detection and PRT header re-attachment, Fixes AB#3568709 feat: eSTS cloud host detection and PRT header re-attachment on WebView return redirects Apr 7, 2026
Copilot AI requested a review from somalaya April 7, 2026 16:45
@github-actions github-actions bot changed the title feat: eSTS cloud host detection and PRT header re-attachment on WebView return redirects feat: eSTS cloud host detection and PRT header re-attachment on WebView return redirects, Fixes AB#3568709 Apr 7, 2026
Copilot AI changed the title feat: eSTS cloud host detection and PRT header re-attachment on WebView return redirects, Fixes AB#3568709 feat: eSTS cloud host detection and PRT header re-attachment on WebView redirects, Fixes AB#3568709 Apr 7, 2026
Copilot AI requested a review from somalaya April 7, 2026 19:58
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 7, 2026

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • dl.google.com
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED -XX:MaxMetaspaceSize=1024m -XX:+HeapDumpOnOutOfMemoryError -Xmx3072m -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en -Duser.variant (dns block)
  • identitydivision.pkgs.visualstudio.com
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED -XX:MaxMetaspaceSize=1024m -XX:+HeapDumpOnOutOfMemoryError -Xmx3072m -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en -Duser.variant (dns block)
  • www.puppycrawl.com
    • Triggering command: /opt/hostedtoolcache/CodeQL/2.25.1/x64/codeql/tools/linux64/java/bin/java /opt/hostedtoolcache/CodeQL/2.25.1/x64/codeql/tools/linux64/java/bin/java -jar /opt/hostedtoolcache/CodeQL/2.25.1/x64/codeql/xml/tools/xml-extractor.jar --fileList=/tmp/codeql-scratch-b2678d0398b00160/dbs/java/working/files-to-index15687347399229976095.list --sourceArchiveDir=/tmp/codeql-scratch-b2678d0398b00160/dbs/java/src --outputDir=/tmp/codeql-scratch-b2678d0398b00160/dbs/java/trap/java (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

private val headers: HashMap<String, String>,
private val span : Span
private val span : Span,
private val fallbackUsername: String? = null
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot , there are compile time errors due to adding private val fallbackUsername: String? = null in ReAttachPrtHeaderHandler

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit 9ebf18d. The root cause was that Kotlin default constructor parameters aren't visible to Java callers without @JvmOverloads. Added @JvmOverloads to both ReAttachPrtHeaderHandler and NonceRedirectHandler constructors — this generates the 3-argument overload Java expects at the processCrossCloudRedirect and processWebCpAuthorize call sites.

Copilot AI and others added 10 commits April 13, 2026 10:01
…so_nonce redirects

When an eSTS redirect URL contains sso_nonce (a common scenario), isNonceRedirect
fires before isEstsCloudHost in the handleUrl() chain. NonceRedirectHandler now
accepts an optional fallbackUsername (passed as mLoginHint from the initial
authorize URL), so PRT re-attachment with the new nonce succeeds even when
login_hint is absent from the redirect URL. Adds NonceRedirectHandlerTest covering
the fallback path and the no-fallback guard.

Agent-Logs-Url: https://github.com/AzureAD/microsoft-authentication-library-common-for-android/sessions/cb6db2b2-56e8-4f9d-ac48-45d3d1aaa520

Co-authored-by: somalaya <69237821+somalaya@users.noreply.github.com>
…andler constructors

Kotlin default constructor parameters are not visible to Java callers without
@jvmoverloads. The two 3-argument Java call sites of ReAttachPrtHeaderHandler
(processCrossCloudRedirect, processWebCpAuthorize) were getting compile errors
because no 3-arg overload existed. @jvmoverloads generates the necessary
overloaded constructors for Java interop. Same annotation added to
NonceRedirectHandler for consistency.

Agent-Logs-Url: https://github.com/AzureAD/microsoft-authentication-library-common-for-android/sessions/f90300a1-6ff8-4474-b169-3d5683a0722e

Co-authored-by: somalaya <69237821+somalaya@users.noreply.github.com>
The feature applies to any eSTS cloud host navigation, not just
return-from-external redirects. Rename the flight constant, string key,
and Javadoc to reflect the actual behavior.
Move the 3-part condition (flight check + eSTS host check + PRT header
check) into a single method for readability in the handleUrl() chain.
- Rename hasMatchingClientId to hasKnownClientId, compare redirect client_id
  against both original request client_id and BROKER_CLIENT_ID constant
- Return false (skip PRT) when /authorize redirect has no client_id
- Add getRefreshTokenCredentialWithBrokerPurpose() to IRefreshTokenCredentialProvider
- Wire purpose:broker path through CommonRefreshTokenCredentialProvider
- ReAttachPrtHeaderHandler: add withPurposeBroker flag, only used for eSTS host redirects
- Add lazy getLoginHintFromRequestUrl() and getClientIdFromRequestUrl() extractors
@somalaya somalaya force-pushed the copilot/ab-3568709-implement-ests-cloud-host-detection branch from 99f44b1 to 481e848 Compare April 15, 2026 05:34
somalaya and others added 3 commits April 16, 2026 15:58
- Remove fallbackUsername parameter from NonceRedirectHandler constructor
- Update NonceRedirectHandlerTest to use 3-param constructor
- Remove unused FALLBACK_USERNAME constant from tests
- Fix pre-existing compile error: add throws ClientException to EstsHostRedirectTest.setup()
@somalaya somalaya marked this pull request as ready for review April 17, 2026 16:06
@somalaya somalaya requested a review from a team as a code owner April 17, 2026 16:06
Copilot AI review requested due to automatic review settings April 17, 2026 16:06
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new flighted WebView path in the Common Android auth library to detect navigations back to an eSTS cloud host and re-attach a fresh PRT credential header (with additional telemetry), aiming to prevent SSO loss after redirects to external Entra domains.

Changes:

  • Introduces a new OpenTelemetry span/attribute and a new CommonFlight flag to gate eSTS-host PRT header re-attachment.
  • Extends PRT credential generation support with an optional “purpose:broker” JWT path and integrates it into WebView redirect handling.
  • Adds/updates Robolectric tests covering eSTS host redirect handling and nonce redirect behavior.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
common4j/src/main/com/microsoft/identity/common/java/opentelemetry/SpanName.java Adds a new span name for eSTS-host PRT header re-attachment telemetry.
common4j/src/main/com/microsoft/identity/common/java/opentelemetry/AttributeName.java Adds a new attribute to record the eSTS redirect host for telemetry.
common4j/src/main/com/microsoft/identity/common/java/interfaces/IRefreshTokenCredentialProvider.kt Adds a new API intended to obtain a refresh token credential JWT with purpose:broker.
common4j/src/main/com/microsoft/identity/common/java/flighting/CommonFlight.java Adds a new flight to gate the eSTS-host PRT re-attachment behavior.
common4j/src/main/com/microsoft/identity/common/java/broker/CommonRefreshTokenCredentialProvider.kt Wires through the new “broker purpose” credential getter.
common/src/main/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/ReAttachPrtHeaderHandler.kt Adds fallback username support and an option to request a purpose:broker credential.
common/src/main/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebViewClient.java Adds eSTS cloud host detection + gated PRT header re-attachment on redirect navigations.
common/src/test/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/NonceRedirectHandlerTest.kt Adds tests around nonce redirect handling and header re-attachment behavior.
common/src/test/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebViewClientEstsHostRedirectTest.java Adds tests for the new flighted eSTS-host redirect handling logic.

Comment on lines +66 to +69
/**
* Span name for PRT header re-attachment when WebView redirects to an eSTS cloud host.
*/
EstsHostRedirectPrtAttach,
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new span name is EstsHostRedirectPrtAttach, but the PR description/requirements reference an “EstsReturnRedirectPrtAttach” span for the return-to-eSTS redirect scenario. Impact: telemetry dashboards/alerts that expect the documented span name won’t light up, and cross-repo consistency can break. Recommendation: align the enum value name (and its usage sites) with the final intended span name, or update the PR description/spec so they match.

Copilot uses AI. Check for mistakes.
// - redirectClientId == null → false
// - redirectClientId matches originalClientId (from mRequestUrl) → true
// - redirectClientId matches BROKER_CLIENT_ID → true
// - exception during extraction → true
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment block describing hasKnownClientId() behavior says “exception during extraction → true”, but the current implementation of hasKnownClientId() returns false on exceptions. Impact: the test documentation is misleading for future maintainers and can cause incorrect assumptions when updating the gating logic. Recommendation: update the comment to reflect the actual behavior (exception → false), or adjust the production code if the intended behavior is different.

Suggested change
// - exception during extraction → true
// - exception during extraction → false

Copilot uses AI. Check for mistakes.
Comment on lines +74 to +88
@Test
fun `processChallenge attaches PRT credential when login_hint is in URL`() {
mockkObject(CommonRefreshTokenCredentialProvider)
every {
CommonRefreshTokenCredentialProvider.getRefreshTokenCredentialUsingNewNonce(
ESTS_URL_WITH_NONCE_AND_HINT, "user@example.com", "testnonce"
)
} returns FRESH_CREDENTIAL

val handler = NonceRedirectHandler(webView, headers, span)
handler.processChallenge(URL(ESTS_URL_WITH_NONCE_AND_HINT))

verify(span).setAttribute(AttributeName.is_new_refresh_token_cred_header_attached.name, true)
verify(webView).loadUrl(ESTS_URL_WITH_NONCE_AND_HINT, headers)
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the success-path test, the provider is stubbed to return a fresh credential, but the test only verifies span attribute + loadUrl call and never asserts that the PRT header value in headers was actually updated to the fresh credential. Impact: the test could pass even if the handler forgets to write the updated header value. Recommendation: assert that headers[PRT_RESPONSE_HEADER] equals the expected refreshed credential after processChallenge().

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in commit 5200dc9. Added assertEquals(FRESH_CREDENTIAL, headers[AuthenticationConstants.Broker.PRT_RESPONSE_HEADER]) to the success-path test so it will now fail if the handler omits writing the updated credential to the headers map.

Comment on lines +1209 to +1215
private boolean shouldReAttachPrtForEstsHost(@NonNull final String url) {
return CommonFlightsManager.INSTANCE.getFlightsProvider()
.isFlightEnabled(CommonFlight.ENABLE_PRT_HEADER_FOR_ESTS_HOST_REDIRECT)
&& isEstsCloudHost(url)
&& hasPrtHeaderAttached()
&& (!url.contains("/authorize") || hasKnownClientId(url));
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldReAttachPrtForEstsHost() uses url.contains("/authorize") on the original (non-lowercased) URL string. Impact: this check is case-sensitive and can misclassify URLs (and also matches query string content), causing the client_id gating to be applied (or skipped) incorrectly and leading to either missed PRT re-attachment or overly broad attachment. Recommendation: parse the URL and check the path segment (similar to isWebCpAuthorizeUrl()) and/or run the check on a lowercased/path-only value rather than doing a raw substring search on the full URL.

Copilot uses AI. Check for mistakes.
Comment on lines +1252 to +1254
} catch (final Throwable e) {
Logger.warn(methodTag, "Failed to process eSTS host redirect. Falling back to loading URL without PRT." + e);
if (span != null) {
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The warning log concatenates the caught Throwable into a non-PII Logger.warn message (..." + e). For URL parsing failures, exception messages can include the full URL/query, which may contain PII (e.g., login_hint). Impact: potential sensitive data leakage into non-PII logs. Recommendation: avoid appending the exception to the warn string; rely on span.recordException(e) for diagnostics, and if logging is needed, log a redacted URL (removeQueryParametersOrRedact(url)) and/or use a PII-safe logger path.

Copilot uses AI. Check for mistakes.
Comment on lines +59 to +69
val username = parameters["login_hint"] ?: fallbackUsername
if (!username.isNullOrEmpty()) {
val updatedRefreshTokenCredentialHeader =
val updatedRefreshTokenCredentialHeader = if (withPurposeBroker) {
CommonRefreshTokenCredentialProvider.getRefreshTokenCredentialWithBrokerPurpose(
url, username
)
} else {
CommonRefreshTokenCredentialProvider.getRefreshTokenCredential(
url, username
)
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReAttachPrtHeaderHandler now has new behavior branches (fallbackUsername and withPurposeBroker) but existing unit tests only exercise the default path calling getRefreshTokenCredential(). Impact: regressions in fallback username handling or purpose:broker JWT generation could ship unnoticed. Recommendation: add unit tests that (1) verify fallbackUsername is used when login_hint is absent in the redirect URL, and (2) verify withPurposeBroker=true calls getRefreshTokenCredentialWithBrokerPurpose and updates the PRT header + telemetry attribute.

Copilot generated this review using guidance from repository custom instructions.
…success path

The success-path test only verified span attribute and loadUrl calls but never
asserted that headers[PRT_RESPONSE_HEADER] was actually updated to the fresh
credential. Added assertEquals(FRESH_CREDENTIAL, headers[PRT_RESPONSE_HEADER])
so the test will catch any regression where the handler fails to write the
updated header value.

Agent-Logs-Url: https://github.com/AzureAD/microsoft-authentication-library-common-for-android/sessions/b10d63cb-fbe7-4144-9666-acc873c4eab0

Co-authored-by: somalaya <69237821+somalaya@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants