Skip to content

Add NativeAuthRequestInterceptor for custom per-request headers Fixes AB#3600652#3112

Open
spetrescu84 wants to merge 12 commits into
devfrom
spetrescu/custom_headers
Open

Add NativeAuthRequestInterceptor for custom per-request headers Fixes AB#3600652#3112
spetrescu84 wants to merge 12 commits into
devfrom
spetrescu/custom_headers

Conversation

@spetrescu84
Copy link
Copy Markdown
Contributor

@spetrescu84 spetrescu84 commented May 13, 2026

Implement custom HTTP headers request interceptor for native auth CIAM requests

  • Add NativeAuthRequestInterceptor interface for injecting custom x-* headers
  • Add NativeAuthHeaderValidator to enforce header naming rules
  • Pass interceptor through the full config propagation chain: OAuth2StrategyParameters → NativeAuthCIAMAuthority → NativeAuthOAuth2Configuration → NativeAuthOAuth2StrategyFactory → all 4 interactors (SignIn, SignUp, ResetPassword, JIT)
  • Add interceptor field to BaseNativeAuthCommandParameters
  • Wire interceptor in NativeAuthMsalController.createOAuth2Strategy()
  • Add unit tests for header validation
  • Add integration tests for interceptor in SignInInteractor

AB#3600652

Implement custom HTTP headers request interceptor for native auth CIAM
requests, matching the iOS PR (microsoft-authentication-library-for-objc#2862).

- Add NativeAuthRequestInterceptor interface for injecting custom x-* headers
- Add NativeAuthHeaderValidator to enforce header naming rules
- Pass interceptor through the full config propagation chain:
  OAuth2StrategyParameters → NativeAuthCIAMAuthority →
  NativeAuthOAuth2Configuration → NativeAuthOAuth2StrategyFactory →
  all 4 interactors (SignIn, SignUp, ResetPassword, JIT)
- Add interceptor field to BaseNativeAuthCommandParameters
- Wire interceptor in NativeAuthMsalController.createOAuth2Strategy()
- Add unit tests for header validation (11 tests)
- Add integration tests for interceptor in SignInInteractor (6 tests)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@spetrescu84 spetrescu84 self-assigned this May 13, 2026
Copilot AI review requested due to automatic review settings May 13, 2026 16:41
@spetrescu84 spetrescu84 requested review from a team as code owners May 13, 2026 16:41
@spetrescu84
Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts in this pull request

Co-authored-by: spetrescu84 <111577419+spetrescu84@users.noreply.github.com>
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 NativeAuthRequestInterceptor extension point that lets applications inject custom x-* HTTP headers (e.g., Akamai/fraud signals) into native-auth (CIAM) network requests. The interceptor is threaded from BaseNativeAuthCommandParameters through OAuth2StrategyParametersNativeAuthCIAMAuthorityNativeAuthOAuth2ConfigurationNativeAuthOAuth2StrategyFactory and into all four interactors (SignIn, SignUp, ResetPassword, JIT). A NativeAuthHeaderValidator enforces the x- prefix and rejects reserved prefixes (x-ms-, x-client-, x-broker-, x-app-).

Changes:

  • New NativeAuthRequestInterceptor interface and NativeAuthHeaderValidator (with unit tests) for additive, validated headers.
  • Interceptor field added to OAuth2StrategyParameters, BaseNativeAuthCommandParameters, NativeAuthOAuth2Configuration, and the four interactors; NativeAuthMsalController.createOAuth2Strategy propagates it; identical applyInterceptorHeaders helper duplicated in each interactor.
  • Integration tests for SignInInteractor (only) covering merge, null/empty interceptor, reserved-prefix filtering, URL passthrough; changelog entry added.

Reviewed changes

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

Show a summary per file
File Description
common4j/.../providers/NativeAuthRequestInterceptor.kt New SAM-style interface for per-request additional headers.
common4j/.../providers/NativeAuthHeaderValidator.kt New validator filtering headers by prefix rules.
common4j/.../providers/NativeAuthOAuth2Configuration.kt Adds nullable requestInterceptor config field.
common4j/.../providers/NativeAuthOAuth2StrategyFactory.kt Passes interceptor into all four interactors.
common4j/.../providers/interactors/SignInInteractor.kt Adds interceptor field + applyInterceptorHeaders and applies to 4 calls.
common4j/.../providers/interactors/SignUpInteractor.kt Same interceptor wiring as SignIn (no tests added).
common4j/.../providers/interactors/ResetPasswordInteractor.kt Same interceptor wiring (no tests added).
common4j/.../providers/interactors/JITInteractor.kt Same interceptor wiring (no tests added).
common4j/.../authorities/NativeAuthCIAMAuthority.kt Threads interceptor from strategy params into config; uses fully-qualified type name.
common4j/.../providers/oauth2/OAuth2StrategyParameters.java Adds mRequestInterceptor; introduces dependency from generic OAuth2 layer to nativeauth package.
common4j/.../commands/parameters/BaseNativeAuthCommandParameters.java Adds transient requestInterceptor field (excluded from equals/hash).
common/.../controllers/NativeAuthMsalController.kt Forwards parameters.requestInterceptor into the strategy params builder.
common4j/.../NativeAuthHeaderValidatorTest.kt New unit tests for the prefix validator.
common4j/.../SignInInteractorRequestInterceptorTest.kt New tests for interceptor behavior, only covering SignIn.
changelog.txt Adds entry with empty PR number (#) and misleading "HttpRequest interceptor" wording.
Comments suppressed due to low confidence (1)

common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/interactors/SignInInteractor.kt:74

  • additionalHeaders is invoked from inside the network call path with no try/catch around it (see applyInterceptorHeaders in each interactor). A buggy interceptor implementation that throws (e.g., NPE while computing a fraud signal) will tear down the entire token request with an unrelated exception type that the existing native-auth error mapping is not designed to handle. Consider catching Throwable from the interceptor invocation, logging it, and falling back to no extra headers so an interceptor crash cannot break sign-in/sign-up/reset-password/JIT for end users.
    private fun applyInterceptorHeaders(requestUrl: URL, headers: Map<String, String?>): Map<String, String?> {
        if (requestInterceptor == null) return headers
        val additionalHeaders = requestInterceptor.additionalHeaders(requestUrl) ?: return headers
        val validHeaders = NativeAuthHeaderValidator.filterValidHeaders(additionalHeaders)
        if (validHeaders.isEmpty()) return headers
        val mergedHeaders = headers.toMutableMap()
        mergedHeaders.putAll(validHeaders)
        return mergedHeaders
    }

Comment thread changelog.txt Outdated
Comment on lines +27 to +69
@@ -61,6 +62,12 @@ public class OAuth2StrategyParameters {
@Nullable
public final List<String> mCapabilities;

/**
* An optional interceptor for injecting custom HTTP headers into native auth requests.
*/
@Nullable
public final transient NativeAuthRequestInterceptor mRequestInterceptor;
Copy link
Copy Markdown
Contributor

Copilot AI commented May 13, 2026

@copilot resolve the merge conflicts in this pull request

Resolved and pushed. I merged origin/dev, fixed the changelog.txt conflict, and finalized the merge commit in f04517b.

@spetrescu84
Copy link
Copy Markdown
Contributor Author

@copilot apply changes based on the comments in this thread

Copy link
Copy Markdown
Contributor

Copilot AI commented May 13, 2026

@copilot apply changes based on the comments in this thread

Applied the linked review-thread updates in d415f28 (plus the main refactor/tests in f450877): shared interceptor-header merge utility used by all 4 interactors, added shared tests, normalized validated custom header names to lowercase with coverage, improved interceptor threading/exception contract docs, removed FQCN usage, decoupled OAuth2StrategyParameters from native-auth type via generic OAuth2RequestInterceptor, and updated changelog wording/PR number.

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:&#43;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:&#43;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-index8567248807499331946.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:

- Add SignUpInteractorRequestInterceptorTest (8 tests)
- Add ResetPasswordInteractorRequestInterceptorTest (7 tests)
- Add JITInteractorRequestInterceptorTest (5 tests)
- Expand SignInInteractorRequestInterceptorTest to cover all 7 methods (12 tests)
- Total: 43 tests (32 interactor + 11 validator), all passing

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

❌ Work item link check failed. Description does not contain AB#{ID}.

Click here to Learn more.

@spetrescu84
Copy link
Copy Markdown
Contributor Author

@copilot apply changes based on the comments in this thread

Copy link
Copy Markdown
Contributor

Copilot AI commented May 13, 2026

@copilot apply changes based on the comments in this thread

Applied. The review-thread items are implemented in the latest commits, including shared interceptor header handling, case-insensitive header normalization/merge behavior, generic OAuth2 interceptor decoupling, and follow-up docs/guards/tests (1611df9, d415f28, f450877).

@spetrescu84
Copy link
Copy Markdown
Contributor Author

@copilot apply changes based on the comments in this thread

Copy link
Copy Markdown
Contributor

Copilot AI commented May 13, 2026

@copilot apply changes based on the comments in this thread

Applied; the linked review-thread changes are already in the branch via 1611df9, d415f28, and f450877 (shared interceptor merge utility + coverage, case-insensitive header normalization/merge behavior, generic OAuth2 interceptor decoupling, and docs/changelog updates).

override fun createOAuth2Strategy(parameters: OAuth2StrategyParameters): NativeAuthOAuth2Strategy {
val config = createNativeAuthOAuth2Configuration(parameters.mChallengeTypes, parameters.mCapabilities)
if (parameters.mRequestInterceptor != null && parameters.mRequestInterceptor !is NativeAuthRequestInterceptor) {
Logger.warn(TAG, "Ignoring non-native OAuth2RequestInterceptor for NativeAuthCIAMAuthority.")
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.

I think WARN message is misleading the situation.

if (existingHeader != null) {
mergedHeaders.remove(existingHeader)
}
mergedHeaders[field] = value
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.

Is it same behaviour in iOS side to overwrite existing headers?

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.

for ((field, value) in validHeaders) {
val existingHeader = mergedHeaders.keys.firstOrNull { it.equals(field, ignoreCase = true) }
if (existingHeader != null) {
mergedHeaders.remove(existingHeader)
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.

Also, this one replaced mandatory or reserved headers also. We want to ensure that we have same behavior with iOS and JS.

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.

The validHeaders already contains the filtered ones as it's done above in
val validHeaders = NativeAuthHeaderValidator.filterValidHeaders(additionalHeaders)
It's the same as iOS does it. First it filters it and only after it iterates.
Added a comment above to clarify

interactor.performIntrospect(createJITIntrospectParams())

assertTrue(capturedHeaders.isCaptured)
assertEquals(2, capturedHeaders.captured.size)
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.

Since actual headers and additional headers size are 2, that could be not a good case for testing. Maybe one of them could have different size, no?

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.

I've updated the base headers to use 3 entries (added x-client-SKU) so that the sizes are asymmetric.

* Tests verifying that [SignUpInteractor] correctly applies custom headers
* from a [NativeAuthRequestInterceptor] to outgoing HTTP requests.
*/
class SignUpInteractorRequestInterceptorTest {
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.

We do not have a test case for changing or overwriting the the reserved headers one. Nice to have this one as well.

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.

Also, no test for case insensitivity. i.e. x-Custom-header and x-custom-Header or x-custom-header.

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.

Put all tests like that as they are connected to logic into RequestInterceptorHeaderUtilsTest
Added for rest of JIT/ResetPassword/SingIn/SignUp the tests that are pertinent. Didn't get to this part before your review.

…behavior, add tests

- NativeAuthCIAMAuthority: Improve misleading WARN log message to clearly
  explain that custom headers will not be applied when interceptor type
  does not match
- RequestInterceptorHeaderUtils: Add detailed KDoc explaining case-insensitive
  merge semantics matching iOS behavior and why reserved SDK headers cannot
  be overwritten
- JITInteractorRequestInterceptorTest: Use 3 base headers (was 2) so null
  interceptor size assertion is more meaningful
- SignUpInteractorRequestInterceptorTest: Add test for reserved header
  overwrite protection and case-insensitive header merge

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
spetrescu84 and others added 2 commits May 14, 2026 17:34
Move shared merge logic tests (reserved filtering, case-insensitive merge,
overwrite protection, URL passthrough, null/empty interceptor) out of
individual interactor tests into RequestInterceptorHeaderUtilsTest.

Each interactor test now only verifies wiring: that the interceptor is
called for each public method and that null interceptor passes through
unchanged. This eliminates ~370 lines of duplicated test code.

Test distribution:
- RequestInterceptorHeaderUtilsTest: 9 tests (shared merge contract)
- NativeAuthHeaderValidatorTest: 12 tests (validation rules)
- 4 interactor tests: 24 tests total (per-method wiring)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@spetrescu84 spetrescu84 changed the title Add NativeAuthRequestInterceptor for custom per-request headers Add NativeAuthRequestInterceptor for custom per-request headers Fixes AB#3600652 May 15, 2026
spetrescu84 and others added 4 commits May 15, 2026 09:42
Each interactor method now has both a merge test and a null-interceptor
test for full symmetry across SignIn, SignUp, ResetPassword, and JIT.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Each test now uses the actual production endpoint URL for its method
(e.g., /signup/v1.0/challenge for performChallenge) instead of a
single shared URL per file.

Co-authored-by: Copilot <223556219+Copilot@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.

5 participants