Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a23bfe0
Add token introspection endpoint and related tests
mikaelweave Dec 3, 2025
fcf508b
Refactor token introspection endpoint handling and add audit event type
mikaelweave Dec 3, 2025
6410e8f
Implement token introspection service and update controller to use it
mikaelweave Dec 5, 2025
e005e0a
Refactor DefaultTokenIntrospectionService to enhance extensibility an…
mikaelweave Dec 5, 2025
afb3e4c
Refactor scope claim retrieval for improved readability and efficiency
mikaelweave Dec 5, 2025
b7b2d53
Refactor TokenIntrospectionControllerTests to improve resource manage…
mikaelweave Dec 5, 2025
a9d08c7
Add TokenIntrospectionTests for RFC 7662 compliance and standard clai…
mikaelweave Dec 5, 2025
fc13cc7
Merge branch 'main' into personal/mikaelw/smart-token-introspection-e…
mikaelweave Dec 5, 2025
1e03cac
Add Token Introspection Tests for RFC 7662 compliance and remove lega…
mikaelweave Dec 6, 2025
2d77cc8
Refactor token introspection service and tests
mikaelweave Dec 8, 2025
f977da1
Remove test for unsupported content type in introspection
mikaelweave Dec 8, 2025
0b6cd2b
Refactor validation logic and update introspection auth
mikaelweave Dec 8, 2025
5b23a53
Add smart user client credentials to E2E test variables and refactor …
mikaelweave Dec 8, 2025
4931b4d
Refactor token request handling in Token Introspection tests for impr…
mikaelweave Dec 9, 2025
5e497a5
Potential fix for code scanning alert no. 2964: Missing Dispose call …
mikaelweave Dec 9, 2025
a6c28ed
Refactor JSON access in TokenIntrospectionTests
mikaelweave Dec 9, 2025
f5e7d63
Refactor TokenIntrospectionControllerTests HttpClient usage
mikaelweave Dec 9, 2025
9567a41
Fix build issues introduced by codeql
mikaelweave Dec 9, 2025
5e7f434
Add named HttpClient for OIDC configuration in DefaultTokenIntrospect…
mikaelweave Dec 11, 2025
a6ea92d
Merge branch 'main' into personal/mikaelw/smart-token-introspection-e…
mikaelweave Dec 11, 2025
cbf335a
Refactor token introspection service documentation for clarity
mikaelweave Dec 11, 2025
ad7f161
Remove unused HttpClient field from TokenIntrospectionControllerTests
mikaelweave Dec 11, 2025
edc90bb
Refactor token introspection to be fully async
mikaelweave Dec 23, 2025
5cf5776
Refactor token introspection endpoint to remove unused cancellation t…
mikaelweave Dec 23, 2025
7ae0d86
Merge branch 'main' into personal/mikaelw/smart-token-introspection-e…
mikaelweave Dec 23, 2025
63c00b8
Refactor token introspection tests; add test configs
mikaelweave Dec 23, 2025
28cc5cc
Merge branch 'personal/mikaelw/smart-token-introspection-endpoint' of…
mikaelweave Dec 23, 2025
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
2 changes: 2 additions & 0 deletions build/jobs/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ steps:
'app_globalReaderUserApp_secret': $(app_globalReaderUserApp_secret)
'app_globalWriterUserApp_id': $(app_globalWriterUserApp_id)
'app_globalWriterUserApp_secret': $(app_globalWriterUserApp_secret)
'app_smartUserClient_id': $(app_smartUserClient_id)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

needed for E2E tests that use SMART client

'app_smartUserClient_secret': $(app_smartUserClient_secret)
'AZURESUBSCRIPTION_CLIENT_ID': $(AzurePipelinesCredential_ClientId)
'AZURESUBSCRIPTION_TENANT_ID': $(AZURESUBSCRIPTION_TENANT_ID)
'AZURESUBSCRIPTION_SERVICE_CONNECTION_ID': $(AZURESUBSCRIPTION_SERVICE_CONNECTION_ID)
Expand Down
2 changes: 1 addition & 1 deletion build/jobs/provision-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ jobs:
numberOfInstances = 2
serviceName = $webAppName
keyVaultName = "${{ parameters.keyVaultName }}".ToLower()
securityAuthenticationAuthority = "https://login.microsoftonline.com/$(tenant-id)"
securityAuthenticationAuthority = "https://sts.windows.net/$(tenant-id-guid)"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Our test env has been using an invalid authority for ... not sure how long. I reuse the authority in the OSS service to check the token so I had to fix the authority.

securityAuthenticationAudience = "${{ parameters.testEnvironmentUrl }}"
additionalFhirServerConfigProperties = $additionalProperties
enableAadSmartOnFhirProxy = $true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ public static IServiceCollection AddDevelopmentIdentityProvider(this IServiceCol
"/AadSmartOnFhirProxy/token");
options.SetAuthorizationEndpointUris("/AadSmartOnFhirProxy/authorize");

// Note: Introspection endpoint is handled by TokenIntrospectionController, not OpenIddict

// Dev flows:
options.AllowAuthorizationCodeFlow();
options.AllowClientCredentialsFlow();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,40 +38,48 @@ public override async Task OnActionExecutionAsync(ActionExecutingContext context

HttpContext httpContext = context.HttpContext;

_parametersValidator.CheckPrettyParameter(httpContext);
_parametersValidator.CheckSummaryParameter(httpContext);
_parametersValidator.CheckElementsParameter(httpContext);
await _parametersValidator.CheckRequestedContentTypeAsync(httpContext);

// If the request is a put or post and has a content-type, check that it's supported
if (httpContext.Request.Method.Equals(HttpMethod.Post.Method, StringComparison.OrdinalIgnoreCase) ||
httpContext.Request.Method.Equals(HttpMethod.Put.Method, StringComparison.OrdinalIgnoreCase))
if (!ShouldIgnoreValidation(httpContext))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This avoids format validation on token introspection endpoints

{
if (httpContext.Request.Headers.TryGetValue(HeaderNames.ContentType, out StringValues headerValue))
_parametersValidator.CheckPrettyParameter(httpContext);
_parametersValidator.CheckSummaryParameter(httpContext);
_parametersValidator.CheckElementsParameter(httpContext);
await _parametersValidator.CheckRequestedContentTypeAsync(httpContext);

// If the request is a put or post and has a content-type, check that it's supported
if (httpContext.Request.Method.Equals(HttpMethod.Post.Method, StringComparison.OrdinalIgnoreCase) ||
httpContext.Request.Method.Equals(HttpMethod.Put.Method, StringComparison.OrdinalIgnoreCase))
{
if (!await _parametersValidator.IsFormatSupportedAsync(headerValue[0]))
if (httpContext.Request.Headers.TryGetValue(HeaderNames.ContentType, out StringValues headerValue))
{
if (!await _parametersValidator.IsFormatSupportedAsync(headerValue[0]))
{
throw new UnsupportedMediaTypeException(string.Format(Api.Resources.UnsupportedHeaderValue, headerValue.FirstOrDefault(), HeaderNames.ContentType));
}
}
else
{
throw new UnsupportedMediaTypeException(string.Format(Api.Resources.UnsupportedHeaderValue, headerValue.FirstOrDefault(), HeaderNames.ContentType));
// If no content type is supplied, then the server should respond with an unsupported media type exception.
throw new UnsupportedMediaTypeException(Api.Resources.ContentTypeHeaderRequired);
}
}
else
else if (httpContext.Request.Method.Equals(HttpMethod.Patch.Method, StringComparison.OrdinalIgnoreCase))
{
// If no content type is supplied, then the server should respond with an unsupported media type exception.
throw new UnsupportedMediaTypeException(Api.Resources.ContentTypeHeaderRequired);
}
}
else if (httpContext.Request.Method.Equals(HttpMethod.Patch.Method, StringComparison.OrdinalIgnoreCase))
{
if (httpContext.Request.Headers.TryGetValue(HeaderNames.ContentType, out StringValues headerValue))
{
if (!await _parametersValidator.IsPatchFormatSupportedAsync(headerValue[0]))
if (httpContext.Request.Headers.TryGetValue(HeaderNames.ContentType, out StringValues headerValue))
{
throw new UnsupportedMediaTypeException(string.Format(Api.Resources.UnsupportedHeaderValue, headerValue.FirstOrDefault(), HeaderNames.ContentType));
if (!await _parametersValidator.IsPatchFormatSupportedAsync(headerValue[0]))
{
throw new UnsupportedMediaTypeException(string.Format(Api.Resources.UnsupportedHeaderValue, headerValue.FirstOrDefault(), HeaderNames.ContentType));
}
}
}
}

await base.OnActionExecutionAsync(context, next);
}

private static bool ShouldIgnoreValidation(HttpContext httpContext)
{
return httpContext.Request.Path.StartsWithSegments("/CustomError", StringComparison.OrdinalIgnoreCase);
}
}
}
Loading
Loading