Skip to content

[azwebpubsub] Fix GenerateClientAccessURL on the TokenCredential path - #27422

Open
Liangying.Wei (vicancy) wants to merge 1 commit into
mainfrom
fix/azwebpubsub-generate-client-access-url
Open

[azwebpubsub] Fix GenerateClientAccessURL on the TokenCredential path#27422
Liangying.Wei (vicancy) wants to merge 1 commit into
mainfrom
fix/azwebpubsub-generate-client-access-url

Conversation

@vicancy

Copy link
Copy Markdown
Member

Fixes #27414.

Context

Issue #27414 reports that GenerateClientAccessURL produces tokens the service rejects, and attributes it to the aud claim using the https scheme instead of wss.

That diagnosis is not correct — the service normalizes the scheme and validates the host and path of the audience. Verified against a live resource (Premium P1) with raw WebSocket upgrade requests, varying only the aud claim:

aud claim Result
https://<host>/client/hubs/chat 101 Switching Protocols
wss://<host>/client/hubs/chat 101 Switching Protocols
http://<host>/client/hubs/chat 101 Switching Protocols
https://<host>client/hubs/chat (missing /) 401
https://<host>/client/hubs/otherhub 401
https://other-resource.webpubsub.azure.com/client/hubs/chat 401

Every other Azure SDK language builds the audience the same way (.NET, JS, Python, Java all put the HTTP(S) endpoint in aud and only switch the returned URL to ws(s)), so the scheme is left unchanged here.

Investigating the report did, however, surface three real defects — all of which make GenerateClientAccessURL unusable for clients created with NewClient (Entra ID).

Fixes

1. Nil-pointer dereference on nil options

GenerateClientAccessURL(ctx, hub, nil) panicked, because options.UserID was read without a nil check on the TokenCredential path. The key-based path handled nil options correctly.

2. minutesToExpire=0 sent by default

ExpirationTimeInMinutes was always forwarded, so leaving it unset sent minutesToExpire=0, which the service rejects:

400 {"code":"Error.BadRequest","message":"MinutesToExpire must be greater than 0","target":"minutesToExpire"}

It now falls back to the same 60 minute default the key-based path already used.

3. Missing trailing-slash normalization

client/hubs/<hub> was appended straight onto the endpoint. ParseConnectionString appends a trailing /, but an endpoint passed to NewClient may not have one — which is the form the portal and az surface. The result was malformed:

endpoint "https://host.webpubsub.azure.com"   ->  wss://host.webpubsub.azure.comclient/hubs/chat
endpoint "https://host.webpubsub.azure.com/"  ->  wss://host.webpubsub.azure.com/client/hubs/chat

Normalization now happens inside the method, matching .NET/JS/Java/Python. This affects the audience of key-signed tokens too, and per the table above a missing separator is exactly the case the service rejects with a 401.

Additionally, a negative ExpirationTimeInMinutes is now rejected on both credential types instead of only the key-based one.

Testing

New client_custom_test.go covers all of the above using a stub transport, so the TokenCredential path is exercised without recordings:

  • endpoint with and without a trailing slash, for both the client URL and the token audience
  • nil options on both credential types
  • minutesToExpire defaulting, and explicit values being honored
  • negative expiration rejected on both credential types
  • userId / role / group forwarded to the service

All tests fail against the current code (assertion failures plus the panic) and pass with the fix. The existing TestClient_GenerateClientAccessURLFromConnectionString continues to pass unchanged, and no recordings needed updating.

`GenerateClientAccessURL` had three defects that made it unusable for clients
created with `NewClient` (Entra ID):

- It panicked with a nil-pointer dereference when `options` was nil, because
  `options.UserID` was read without a nil check. The key-based path handled nil
  options correctly.
- It always forwarded `ExpirationTimeInMinutes` to the service, so the default
  zero value was sent as `minutesToExpire=0`. The service rejects that with
  `400 Error.BadRequest: MinutesToExpire must be greater than 0`. It now falls
  back to the same 60 minute default the key-based path uses.
- It appended `client/hubs/<hub>` directly to the endpoint without normalizing a
  trailing slash, producing a malformed audience and client URL such as
  `wss://<host>client/hubs/<hub>`. `ParseConnectionString` adds the trailing
  slash, but an endpoint passed to `NewClient` may not have one. The other Azure
  SDK languages normalize this inside the token method, and this change does the
  same.

A negative `ExpirationTimeInMinutes` is now also rejected on both credential
types rather than only the key-based one.

Note that the audience scheme is intentionally left as `http(s)`. The service
normalizes the scheme and validates the host and path, and every other Azure SDK
language builds the audience the same way.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: bf666f99-8d30-4763-878d-b566c0ac2f2d
Copilot AI balanced review requested due to automatic review settings August 20, 2026 04:58
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
6 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

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.

Pull request overview

Fixes GenerateClientAccessURL behavior for Entra ID clients and normalizes generated WebSocket URLs.

Changes:

  • Handles nil options, default expiration, and negative values consistently.
  • Normalizes endpoint trailing slashes.
  • Adds regression tests and changelog entries.
Show a summary per file
File Description
client_custom.go Corrects URL generation and option handling.
client_custom_test.go Adds regression coverage for corrected behavior.
CHANGELOG.md Documents the fixes.

Review details

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 3/3 changed files
  • Comments generated: 0
  • Review effort level: Balanced

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Web PubSub: GenerateClientAccessURL generates invalid tokens

3 participants