Add NativeAuthRequestInterceptor for custom per-request headers Fixes AB#3600652#3112
Add NativeAuthRequestInterceptor for custom per-request headers Fixes AB#3600652#3112spetrescu84 wants to merge 12 commits into
Conversation
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>
|
@copilot resolve the merge conflicts in this pull request |
Co-authored-by: spetrescu84 <111577419+spetrescu84@users.noreply.github.com>
There was a problem hiding this comment.
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 OAuth2StrategyParameters → NativeAuthCIAMAuthority → NativeAuthOAuth2Configuration → NativeAuthOAuth2StrategyFactory 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
NativeAuthRequestInterceptorinterface andNativeAuthHeaderValidator(with unit tests) for additive, validated headers. - Interceptor field added to
OAuth2StrategyParameters,BaseNativeAuthCommandParameters,NativeAuthOAuth2Configuration, and the four interactors;NativeAuthMsalController.createOAuth2Strategypropagates it; identicalapplyInterceptorHeadershelper 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
additionalHeadersis invoked from inside the network call path with notry/catcharound it (seeapplyInterceptorHeadersin 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 catchingThrowablefrom 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
}
| @@ -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; | |||
Resolved and pushed. I merged |
|
@copilot apply changes based on the comments in this thread |
Agent-Logs-Url: https://github.com/AzureAD/microsoft-authentication-library-common-for-android/sessions/3a466cba-db2f-42cd-afa9-92e96cef6792 Co-authored-by: spetrescu84 <111577419+spetrescu84@users.noreply.github.com>
Agent-Logs-Url: https://github.com/AzureAD/microsoft-authentication-library-common-for-android/sessions/3a466cba-db2f-42cd-afa9-92e96cef6792 Co-authored-by: spetrescu84 <111577419+spetrescu84@users.noreply.github.com>
Applied the linked review-thread updates in 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:
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>
|
❌ Work item link check failed. Description does not contain AB#{ID}. Click here to Learn more. |
|
@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 ( |
|
@copilot apply changes based on the comments in this thread |
Applied; the linked review-thread changes are already in the branch via |
| 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.") |
There was a problem hiding this comment.
I think WARN message is misleading the situation.
| if (existingHeader != null) { | ||
| mergedHeaders.remove(existingHeader) | ||
| } | ||
| mergedHeaders[field] = value |
There was a problem hiding this comment.
Is it same behaviour in iOS side to overwrite existing headers?
There was a problem hiding this comment.
| for ((field, value) in validHeaders) { | ||
| val existingHeader = mergedHeaders.keys.firstOrNull { it.equals(field, ignoreCase = true) } | ||
| if (existingHeader != null) { | ||
| mergedHeaders.remove(existingHeader) |
There was a problem hiding this comment.
Also, this one replaced mandatory or reserved headers also. We want to ensure that we have same behavior with iOS and JS.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
We do not have a test case for changing or overwriting the the reserved headers one. Nice to have this one as well.
There was a problem hiding this comment.
Also, no test for case insensitivity. i.e. x-Custom-header and x-custom-Header or x-custom-header.
There was a problem hiding this comment.
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>
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>
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>
Implement custom HTTP headers request interceptor for native auth CIAM requests
AB#3600652