From 8139dc12ae7522070fe9604de8b33806d2c6a845 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Wed, 14 Oct 2026 14:12:56 +0200 Subject: [PATCH 1/2] fix(auth)!: decode omitted OAuthClient fields and drop the always-null scope --- MIGRATION.md | 19 ++++++++++++++ .../supabase_auth/lib/src/types/types.dart | 26 ++++++++----------- .../test/src/types/oauth_client_test.dart | 17 ++++++++++-- sdk-compliance.yaml | 1 - 4 files changed, 45 insertions(+), 18 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 060026138..16f753f69 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -245,6 +245,25 @@ final String name = client.clientName ?? client.clientId; `admin.oauth.createClient` always have one. Clients that register themselves through the dynamic client registration API, where the name is optional, may not. +### `OAuthClient.tokenEndpointAuthenticationMethod` and `registrationType` are nullable + +Auth leaves `token_endpoint_auth_method` and `registration_type` out of a client response when they +are empty, so `OAuthClient.tokenEndpointAuthenticationMethod` is a `String?` and +`OAuthClient.registrationType` is an `OAuthClientRegistrationType?`. + +```dart +// Before +final OAuthClientRegistrationType type = client.registrationType; + +// After +final OAuthClientRegistrationType? type = client.registrationType; +``` + +### `OAuthClient.scope` is gone + +Auth never returns a scope for an OAuth client, so `OAuthClient.scope` was always `null`. Remove +any reads of it. The scopes a client requests are on `OAuthAuthorizationDetailsResponse.scope`. + ### `JWK.publicKey` returns a `JWTKey` `JWK.publicKey` supports EC keys as well as RSA keys, so it returns a `JWTKey`: an `RSAPublicKey` diff --git a/packages/supabase_auth/lib/src/types/types.dart b/packages/supabase_auth/lib/src/types/types.dart index 53df0616c..6ba74d2c5 100644 --- a/packages/supabase_auth/lib/src/types/types.dart +++ b/packages/supabase_auth/lib/src/types/types.dart @@ -227,14 +227,13 @@ class OAuthClient { this.clientName, this.clientSecret, required this.clientType, - required this.tokenEndpointAuthenticationMethod, - required this.registrationType, + this.tokenEndpointAuthenticationMethod, + this.registrationType, this.clientUri, this.logoUri, required this.redirectUris, required this.grantTypes, required this.responseTypes, - this.scope, required this.createdAt, required this.updatedAt, }); @@ -246,10 +245,11 @@ class OAuthClient { clientSecret: json['client_secret'] as String?, clientType: OAuthClientType.fromValue(json['client_type'] as String), tokenEndpointAuthenticationMethod: - json['token_endpoint_auth_method'] as String, - registrationType: OAuthClientRegistrationType.fromValue( - json['registration_type'] as String, - ), + json['token_endpoint_auth_method'] as String?, + registrationType: switch (json['registration_type']) { + final String value => OAuthClientRegistrationType.fromValue(value), + _ => null, + }, clientUri: json['client_uri'] as String?, logoUri: json['logo_uri'] as String?, redirectUris: (json['redirect_uris'] as List?)?.cast() ?? const [], @@ -270,7 +270,6 @@ class OAuthClient { ), ) .toList(), - scope: json['scope'] as String?, createdAt: parseIso8601(json, 'created_at'), updatedAt: parseIso8601(json, 'updated_at'), ); @@ -288,11 +287,11 @@ class OAuthClient { /// Type of OAuth client final OAuthClientType clientType; - /// Token endpoint authentication method - final String tokenEndpointAuthenticationMethod; + /// Token endpoint authentication method, if the server returned one + final String? tokenEndpointAuthenticationMethod; - /// Registration type of the client - final OAuthClientRegistrationType registrationType; + /// Registration type of the client, if the server returned one + final OAuthClientRegistrationType? registrationType; /// URI of the OAuth client final String? clientUri; @@ -309,9 +308,6 @@ class OAuthClient { /// Array of allowed response types final List responseTypes; - /// Scope of the OAuth client - final String? scope; - /// Timestamp when the client was created final DateTime createdAt; diff --git a/packages/supabase_auth/test/src/types/oauth_client_test.dart b/packages/supabase_auth/test/src/types/oauth_client_test.dart index c9fc200e9..00a55bf3a 100644 --- a/packages/supabase_auth/test/src/types/oauth_client_test.dart +++ b/packages/supabase_auth/test/src/types/oauth_client_test.dart @@ -14,7 +14,6 @@ void main() { 'redirect_uris': ['https://example.com/callback'], 'grant_types': ['authorization_code', 'refresh_token'], 'response_types': ['code'], - 'scope': 'openid profile email', 'created_at': '2025-01-01T00:00:00.000Z', 'updated_at': '2025-01-02T03:04:05.000Z', }; @@ -35,7 +34,6 @@ void main() { OAuthClientGrantType.refreshToken, ]); expect(client.responseTypes, [OAuthClientResponseType.code]); - expect(client.scope, 'openid profile email'); expect(client.createdAt, DateTime.parse('2025-01-01T00:00:00.000Z')); expect(client.updatedAt, DateTime.parse('2025-01-02T03:04:05.000Z')); }); @@ -53,6 +51,21 @@ void main() { expect(client.logoUri, isNull); }); + test( + 'fromJson parses a client without a token endpoint authentication ' + 'method or registration type', + () { + final client = OAuthClient.fromJson( + {...json} + ..remove('token_endpoint_auth_method') + ..remove('registration_type'), + ); + + expect(client.tokenEndpointAuthenticationMethod, isNull); + expect(client.registrationType, isNull); + }, + ); + test( 'fromJson parses a client without redirect URIs, grant types or ' 'response types', diff --git a/sdk-compliance.yaml b/sdk-compliance.yaml index 6aac98d25..2ac27f0ed 100644 --- a/sdk-compliance.yaml +++ b/sdk-compliance.yaml @@ -2894,7 +2894,6 @@ supporting_symbols: - OAuthClient.redirectUris - OAuthClient.registrationType - OAuthClient.responseTypes - - OAuthClient.scope - OAuthClient.tokenEndpointAuthenticationMethod - OAuthClient.updatedAt - OAuthClientGrantType From 2578412952597771883777f3b77984ac7780f180 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Wed, 14 Oct 2026 14:14:41 +0200 Subject: [PATCH 2/2] fix(auth)!: drop the scope option from OAuth client create and update --- MIGRATION.md | 30 +++++++++++++++++-- .../supabase_auth/lib/src/types/types.dart | 10 ------- .../test/src/auth_admin_oauth_api_test.dart | 1 - .../test/src/auth_oauth_api_test.dart | 7 ++--- sdk-compliance.yaml | 2 -- 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 16f753f69..b054e6f94 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -259,10 +259,34 @@ final OAuthClientRegistrationType type = client.registrationType; final OAuthClientRegistrationType? type = client.registrationType; ``` -### `OAuthClient.scope` is gone +### OAuth clients no longer have a `scope` -Auth never returns a scope for an OAuth client, so `OAuthClient.scope` was always `null`. Remove -any reads of it. The scopes a client requests are on `OAuthAuthorizationDetailsResponse.scope`. +Auth does not store a scope on an OAuth client, so `OAuthClient.scope`, +`CreateOAuthClientOptions.scope` and `UpdateOAuthClientOptions.scope` are gone. `OAuthClient.scope` +was always `null`, and Auth ignored the scope sent when creating or updating a client. + +Scopes belong to each authorization request instead. A client asks for them with the `scope` +query parameter on `/oauth/authorize`, and the scopes it asked for are on +`OAuthAuthorizationDetailsResponse.scope`. + +```dart +// Before +await supabase.auth.admin.oauth.createClient( + CreateOAuthClientOptions( + clientName: 'My App', + redirectUris: ['https://example.com/callback'], + scope: 'openid email', + ), +); + +// After +await supabase.auth.admin.oauth.createClient( + CreateOAuthClientOptions( + clientName: 'My App', + redirectUris: ['https://example.com/callback'], + ), +); +``` ### `JWK.publicKey` returns a `JWTKey` diff --git a/packages/supabase_auth/lib/src/types/types.dart b/packages/supabase_auth/lib/src/types/types.dart index 6ba74d2c5..209e58bd9 100644 --- a/packages/supabase_auth/lib/src/types/types.dart +++ b/packages/supabase_auth/lib/src/types/types.dart @@ -337,7 +337,6 @@ class CreateOAuthClientOptions { required this.redirectUris, this.grantTypes, this.responseTypes, - this.scope, }); /// Human-readable name of the OAuth client @@ -359,9 +358,6 @@ class CreateOAuthClientOptions { /// Array of allowed response types (optional, defaults to code) final List? responseTypes; - /// Scope of the OAuth client - final String? scope; - Map toJson() { return { 'client_name': clientName, @@ -378,7 +374,6 @@ class CreateOAuthClientOptions { OAuthClientResponseType.unknown, 'responseTypes', ), - 'scope': ?scope, }; } } @@ -393,7 +388,6 @@ class UpdateOAuthClientOptions { this.redirectUris, this.grantTypes, this.responseTypes, - this.scope, }); /// Human-readable name of the OAuth client @@ -414,9 +408,6 @@ class UpdateOAuthClientOptions { /// Array of allowed response types final List? responseTypes; - /// Scope of the OAuth client - final String? scope; - Map toJson() { return { 'client_name': ?clientName, @@ -433,7 +424,6 @@ class UpdateOAuthClientOptions { OAuthClientResponseType.unknown, 'responseTypes', ), - 'scope': ?scope, }; } } diff --git a/packages/supabase_auth/test/src/auth_admin_oauth_api_test.dart b/packages/supabase_auth/test/src/auth_admin_oauth_api_test.dart index 4e6f54275..cdb332caa 100644 --- a/packages/supabase_auth/test/src/auth_admin_oauth_api_test.dart +++ b/packages/supabase_auth/test/src/auth_admin_oauth_api_test.dart @@ -43,7 +43,6 @@ void main() { clientName: 'Test OAuth Client', redirectUris: ['https://example.com/callback'], clientUri: 'https://example.com', - scope: 'openid profile email', ); final response = await client.admin.oauth.createClient(parameters); diff --git a/packages/supabase_auth/test/src/auth_oauth_api_test.dart b/packages/supabase_auth/test/src/auth_oauth_api_test.dart index 252c35593..eeb496c99 100644 --- a/packages/supabase_auth/test/src/auth_oauth_api_test.dart +++ b/packages/supabase_auth/test/src/auth_oauth_api_test.dart @@ -205,7 +205,6 @@ void main() { clientName: 'Test OAuth Client', redirectUris: ['http://127.0.0.1:3000/oauth/callback'], responseTypes: [OAuthClientResponseType.code], - scope: 'email', ); final oauthClient = await fixture.createOAuthClient(clientParameters); final auth = await fixture.logIn(password: password, email: email1); @@ -218,7 +217,7 @@ void main() { expect(response, isA()); final details = response as OAuthAuthorizationDetailsResponse; expect(details.authorizationId, equals(authorizationId)); - expect(details.scope, equals(clientParameters.scope)); + expect(details.scope, equals('email')); expect(details.redirectUri, equals(clientParameters.redirectUris.first)); expect(details.client.clientId, equals(oauthClient.clientId)); expect(details.client.clientName, equals(oauthClient.clientName)); @@ -320,7 +319,6 @@ void main() { clientName: 'Test OAuth Client', redirectUris: ['http://127.0.0.1:3000/oauth/callback'], responseTypes: [OAuthClientResponseType.code], - scope: 'email', ); final oauthClient = await fixture.createOAuthClient(clientParameters); await fixture.logIn(password: password, email: email1); @@ -341,7 +339,6 @@ void main() { clientName: 'Test OAuth Client', redirectUris: ['http://127.0.0.1:3000/oauth/callback'], responseTypes: [OAuthClientResponseType.code], - scope: 'email', ); final oauthClient = await fixture.createOAuthClient(clientParameters); await fixture.logIn(password: password, email: email1); @@ -364,7 +361,6 @@ void main() { clientName: 'Test OAuth Client', redirectUris: ['http://127.0.0.1:3000/oauth/callback'], responseTypes: [OAuthClientResponseType.code], - scope: 'email', ); final oauthClient = await fixture.createOAuthClient(clientParameters); await fixture.logIn(password: password, email: email1); @@ -441,6 +437,7 @@ class AuthOauthApiFixture { 'redirect_uri': client.redirectUris.first, 'code_challenge': 'pkqGrzhFuuBOcRoR4elYl2ki1EiR3KtBdtQsEZdv8rM', 'code_challenge_method': 'S256', + 'scope': 'email', }, ), )..followRedirects = false; diff --git a/sdk-compliance.yaml b/sdk-compliance.yaml index 2ac27f0ed..82bc32ac5 100644 --- a/sdk-compliance.yaml +++ b/sdk-compliance.yaml @@ -750,7 +750,6 @@ features: - CreateOAuthClientOptions.logoUri - CreateOAuthClientOptions.redirectUris - CreateOAuthClientOptions.responseTypes - - CreateOAuthClientOptions.scope - CreateOAuthClientOptions.toJson auth.oauth_admin.get_client: status: implemented @@ -769,7 +768,6 @@ features: - UpdateOAuthClientOptions.logoUri - UpdateOAuthClientOptions.redirectUris - UpdateOAuthClientOptions.responseTypes - - UpdateOAuthClientOptions.scope - UpdateOAuthClientOptions.toJson auth.oauth_admin.delete_client: status: implemented