Repository navigation
WebApi: apply the REST auth opt-ins to the opcua+openapi WebSocket upgrade - #4699
Conversation
…grade The AddWebApi*Auth opt-ins are enforced on the REST routes; the plain opcua+openapi WebSocket upgrade is dispatched by the listener after routing and now runs the same default authorization policy before it is accepted. The principal of either opcua+openapi variant is mapped through ISessionlessIdentityProvider into SecureChannelContext.UpstreamIdentity, a credential whose exp has passed is refused at upgrade, and the socket is closed when it expires. Refused upgrades now complete their HTTP response (Connection: close) instead of resetting the connection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Authorization behavior can diverge from REST policies, and long-lived credentials bypass socket expiry enforcement.
Review effort: Balanced
Findings: 1
Open (4)
What changed in this PR
Extends Web API authentication to OpenAPI WebSocket upgrades and propagates authenticated identities into channel contexts.
Changes:
- Enforces REST authorization policies during plain OpenAPI upgrades.
- Propagates upstream identity and enforces token expiry.
- Adds real-listener authentication and expiry integration tests.
| File | Description |
|---|---|
WebApiHttpsStartupContributor.cs |
Adds upgrade authentication and identity resolution. |
HttpsTransportListener.cs |
Handles authorization failures, identity propagation, and credential expiry. |
RealHttpsListenerIntegrationTests.Authentication.cs |
Tests WebSocket authentication, identity, and expiry behavior. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- Authorize the upgrade with the HttpContext as the resource and answer a failure through IAuthorizationMiddlewareResultHandler, so challenge and forbid use the default policy's schemes exactly as on the REST routes. - Re-arm the credential expiry wait until the actual expiry instead of skipping lifetimes beyond a single timer wait. - Run the expiry on an injectable TimeProvider; the test drives it with a FakeTimeProvider instead of racing the handshake against a short token, and covers a lifetime beyond a single timer wait. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Code coverage✅ Coverage gate passed.
Uncovered changed lines
Coverage is above the recorded baseline - consider ratcheting Thresholds live in |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #4699 +/- ##
==========================================
- Coverage 82.83% 82.82% -0.01%
==========================================
Files 2262 2262
Lines 344665 344776 +111
Branches 60057 60078 +21
==========================================
+ Hits 285491 285556 +65
- Misses 39150 39188 +38
- Partials 20024 20032 +8
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Covers an opcua+openapi upgrade whose authenticator or identity resolver throws, and GetCredentialExpiry for whole and fractional exp values, out-of-range clamping, unusable claims and unauthenticated principals. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@copilot resolve the merge conflicts in this pull request |
Merged |
…-openapi-auth # Conflicts: # tests/Opc.Ua.Bindings.Https.WebApi.Tests/RealHttpsListenerIntegrationTests.Authentication.cs
A refused WebSocket upgrade on a connection-admitted request closed the connection's physical lease while leaving the connection alive: HTTP/2 keeps it usable for other streams, so it was no longer counted or closed by admission shutdown. The refused upgrade now completes the lease's handshake and leaves it to RunHttpsConnectionAsync, which releases it when the connection really closes. A request-scoped lease is released as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>


Description
Follow-up to #4674 for the WebSocket side of the OpenAPI binding.
opcua+openapiupgrade uses the REST auth. WhenAddWebApiBasicAuth/AddWebApiBearerAuth/AddWebApiMutualTlsAuthis configured, the upgrade runs the same default authorization policy as the REST route group (RequireAuthorization()), because the upgrade is dispatched by the listener after routing. Requests that don't satisfy it get the schemes' challenge (401 +WWW-Authenticate) or 403. Without an auth opt-in the behaviour is unchanged.opcua+openapiandopcua+openapi+<accesstoken>, the authenticated principal is mapped throughISessionlessIdentityProviderintoSecureChannelContext.UpstreamIdentity, as the REST routes already do per request.expclaim has already passed is refused at upgrade, also within the JwtBearer clock skew, and the socket is closed when the token expires.Connection: closeand Kestrel closes the connection after sending the response.The other WebSocket sub-protocols (
opcua+uacp,opcua+uajson) and the HTTPS binary/JSON endpoint are unchanged: per Part 6 §7.4 they authenticate through the OPC UA session layer, not HTTP credentials.Tests
New cases in
RealHttpsListenerIntegrationTests.Authentication.csagainst a realHttpsTransportListener:UpstreamIdentityset for bothopcua+openapivariants;Authorizationheader and in the sub-protocol;Locally on net10.0:
Opc.Ua.Bindings.Https.WebApi.Tests499/499,Opc.Ua.Core.Tests(Wss/OpenApi/Https) 243/243,Opc.Ua.Sessions.Tests(Wss/Https/WebApi) 53/53.Opc.Ua.Bindings.Httpsbuilds on all TFMs with 0 warnings.Related Issues
Checklist
🤖 Generated with Claude Code