OAuthClientMetadata serializes unset optional fields as null — auth servers reject the client-metadata document (invalid_client_metadata) #41
Labels
No labels
breaking-change
bug
documentation
duplicate
enhancement
good first issue
help wanted
performance
question
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Grandiras/ATProto.NET#41
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
OAuthClientMetadatais clearly meant to be serialized directly into the OAuth client-metadata document — every property carries a[JsonPropertyName]. But serializing it emits unset optional fields as JSONnull, and ATProto authorization servers reject that: the spec distinguishes absent from null, so optional fields must be omitted.So any consumer that serves
JsonSerializer.Serialize(metadata)at itsclient_idURL getsinvalid_client_metadataand can't complete OAuth.Repro
OAuthClientMetadata, leavingLogoUri,TosUri,PolicyUri,TokenEndpointAuthSigningAlg,Jwks,JwksUriunset.Results.Json(metadata)/JsonSerializer.Serialize(metadata)at theclient_idURL.OAuthClient.StartAuthorizationAsync(...).PAR fails (observed against the reference
@atproto/oauth-provider/ Bluesky PDS):Suggested fix
Annotate the optional properties with
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)], or expose a serializer helper /ToJson()that appliesDefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, so serializing the metadata yields a spec-compliant document out of the box.Workaround
Serialize with
new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }.Environment
ATProtoNet 0.4.0, net10.0.
🤖 On it — follow along in the action run; the reply will link the full session transcript.
Fixed and pushed to branch
claude/issue-41(commitd67d790).What changed
src/ATProtoNet/Auth/OAuth/OAuthModels.csOAuthClientMetadatanow carries[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]:client_name,client_uri,logo_uri,tos_uri,policy_uri,token_endpoint_auth_signing_alg,jwks,jwks_uri. This is the attribute-based variant of the suggested fix, so it holds under anyJsonSerializerOptions— including the ASP.NET Core defaults behindResults.Json(...)— with no consumer change.JsonWebKey(crv,x,y,kid,use,alg). A confidential client embedding ajwkswould otherwise hit the identical rejection one level down, e.g."use": nullinside a key.OAuthClientMetadata.ToJson(bool writeIndented = false)for serving the document directly:Tests — four new cases in
OAuthModelsTests: that serializing with stockJsonSerializer.Serialize(metadata)(no options) omits each of the six fields named in the error output while still emitting the set ones; thatToJson()andToJson(writeIndented: true)produce null-free, round-trippable documents; and that nested JWK optionals are omitted too.Docs/CHANGELOG —
docs/oauth.md§1 now shows serving the document from the configured metadata object and notes the absent-vs-null distinction; CHANGELOG gets entries underAdded(ToJson) andFixed.Verified
Built the full solution and ran the unit suite on .NET 10 (SDK 10.0.302, installed in the container):
dotnet build -p:EnableSourceControlManagerQueries=false→ Build succeeded, no new warnings on the touched files.dotnet test tests/ATProtoNet.Tests/ -p:EnableSourceControlManagerQueries=false→ 1019 passed, 0 failed (up from 1015; the 4 new tests fail against the pre-fix model).Not verified end-to-end against a live authorization server — the integration tests are PDS-gated and no PDS is reachable here — so the PAR round-trip against
@atproto/oauth-provideris covered only by the serialization assertions matching the exact field names in your error output.⏱ 3m 48s · 33 turns · tokens: 43.3k in / 12.2k out (+1460.4k cache-read)
📜 Full session transcript
→ Opened PR: #58