Skip to content

feat(auth): update a custom OAuth/OIDC provider as an admin - #600

Merged
Tr00d merged 1 commit into
masterfrom
feat/auth.admin.update_provider
Oct 29, 2026
Merged

Tr00d merged 1 commit into
masterfrom
feat/auth.admin.update_provider

Conversation

@Tr00d

@Tr00d Tr00d commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Admins can update a custom OAuth/OIDC provider. Only the settings they set are sent; every other setting keeps its current value.

Capability: auth.admin.update_provider: Admin Update Provider.

Standalone: branches off master, which already has the shared CustomProvider types from #590. Its only overlap with the other custom-provider PRs is where each adds its member to Api, AdminClient, the two interfaces and PublicAPI.Unshipped.txt; whichever merges second rebases and keeps both sides.

Public API

// IGotrueAdminClient<TUser>
Task<CustomProvider> UpdateCustomProviderAsync(string identifier, CustomProviderChanges changes, CancellationToken cancellationToken = default);

New type in Supabase.Gotrue.UpdateCustomProvider:

Type Members
CustomProviderChanges (init-only record, every member optional) Name, ClientId, ClientSecret, Issuer, AuthorizationUrl, TokenUrl, UserInfoUrl, Options (Supabase.Gotrue.Common.CustomProviderOptions, shared with create)
using Supabase.Gotrue.Common;
using Supabase.Gotrue.UpdateCustomProvider;

var admin = new AdminClient(serviceRoleKey, new ClientOptions { Url = authUrl });
var updated = await admin.UpdateCustomProviderAsync("custom:mycompany", new CustomProviderChanges
{
    Name = "My Company",
    Options = new CustomProviderOptions { Enabled = false },
});

IGotrueApi<TUser, TSession> gains the matching UpdateCustomProviderAsync(string jwt, string identifier, CustomProviderChanges, CancellationToken).

Behavior

Operation PUT /admin/custom-providers/{identifier} with the service-role key
Input identifier, required, starting with custom:. changes, required. Null members are left out of the body, so the server keeps those values. Type and identifier cannot change. Settings of the other protocol are ignored by the server. Changing the issuer or discovery URL of an OIDC provider makes the server fetch and validate the discovery document again. If nothing is set, the server returns the provider unchanged.
Output 200 → the updated CustomProvider
Checks before the request ArgumentNullException for null changes. ArgumentException (the same check as get and delete) when the identifier lacks the case-sensitive custom: prefix or contains /, \, ?, # or %.
Failures GotrueException: Reason = CustomProviderNotFound on 404 custom_provider_not_found; ErrorCode = validation_failed on 400 (an invalid setting, a non-HTTPS or loopback URL, an OIDC discovery failure); Reason = AdminTokenRequired on 401/403; ErrorCode = feature_disabled on 404. A null body also throws GotrueException.
State None

CustomProviderChanges.ToString() leaves out the client secret.

Deviations from supabase-js

  • supabase-js puts the identifier into the URL raw. This SDK rejects identifiers that would change the route before sending anything.
  • The optional settings are grouped in the CustomProviderOptions record shared with create, instead of one flat parameter bag.

Tests

The E2E test asserts on the provider UpdateCustomProviderAsync returns, so it doesn't depend on GetCustomProviderAsync.

Tier Class Pins
E2E UpdateCustomProviderTests An update applies the fields sent and keeps every other setting (read back with GetCustomProviderAsync). Unknown identifier → CustomProviderNotFound. Non-admin key → AdminTokenRequired.
Contract UpdateCustomProviderContractTests Route, method and service-role credentials. Response mapping. 404 → CustomProviderNotFound. null body. Null changes rejected before I/O. A cancelled token sends nothing. The call completes when the caller blocks on a context that never runs continuations.
Contract (approval) UpdateCustomProviderApprovalTests Exact body with only a name changed ({"name":"Renamed"}) and with every member set
Unit UpdateCustomProviderIdentifierTests, CustomProviderChangesTests Identifiers that would change the route are rejected. The secret is kept out of ToString().

Changed-line coverage: 100% (38 lines, measured against master). Mutation: 5/5 detected on changed lines (4 killed, 1 timeout from the deadlock test), 0 survivors (1 compile error, 2 ignored). The local mutation run predates the split; the production code is unchanged and the mutation check on this PR reruns it.

Layout

packages/Gotrue/Gotrue/UpdateCustomProvider/
  CustomProviderChanges.cs
  UpdateCustomProviderBody.cs   internal: update's wire body, built on Common/CustomProviderOptionsBody (in `master` since #590)
packages/Gotrue/Gotrue.Tests/UpdateCustomProvider/   E2E, contract, approval, unit tests, Data/*.verified.txt

Update reuses Common/ (CustomProviderOptions, CustomProviderOptionsBody, CustomProvider) as it is and moves nothing.

Notes

@Tr00d
Tr00d force-pushed the feat/auth.admin.update_provider branch from 1a35040 to c49af92 Compare October 29, 2026 15:18
@Tr00d
Tr00d marked this pull request as ready for review October 29, 2026 15:23
@Tr00d
Tr00d merged commit 16a3d2a into master Oct 29, 2026
3 checks passed
@Tr00d
Tr00d deleted the feat/auth.admin.update_provider branch October 29, 2026 15:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

parity(auth): add custom_claims_allowlist to custom providers admin API [from supabase-js]

1 participant