Skip to content

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

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

Add NativeAuthRequestInterceptor for custom per-request headers Fixes AB#3600652#3112
spetrescu84 wants to merge 15 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 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 all interactors

API Change: NativeAuthRequestInterceptor moved to MSAL

The NativeAuthRequestInterceptor interface has been moved to the MSAL module (com.microsoft.identity.nativeauth package) as it is a public-facing type that customers implement. Common now uses the base OAuth2RequestInterceptor type internally, which NativeAuthRequestInterceptor extends. This follows the established pattern where public nativeauth types live in MSAL.

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 <[email protected]>
@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

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 <[email protected]>
@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.

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 changed how this works, please take a look

…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 <[email protected]>
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 <[email protected]>
@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 <[email protected]>
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 <[email protected]>
Replace all usages of NativeAuthRequestInterceptor in common with the
base OAuth2RequestInterceptor type. The NativeAuthRequestInterceptor
interface is a public-facing API that customers implement, so it belongs
in the MSAL module (com.microsoft.identity.nativeauth package) following
the established pattern for public nativeauth types.

Common now uses OAuth2RequestInterceptor internally, which is the parent
interface with the same contract. Any MSAL-level NativeAuthRequestInterceptor
implementation will satisfy this type since it extends OAuth2RequestInterceptor.

Co-authored-by: Copilot <[email protected]>
Copy link
Copy Markdown
Contributor

@mustafamizrak mustafamizrak left a comment

Choose a reason for hiding this comment

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

Why did you move NativeAuthRequestInterceptor to MSAL module? What are the reasons?

@antrix1989
Copy link
Copy Markdown

Why did you move NativeAuthRequestInterceptor to MSAL module? What are the reasons?

We use NativeAuthRequestInterceptor in public api, that is why we moved it to the main MSAL module. Common core code should not be exposed in the public api.

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