getClientMetadata function
Retrieves OAuth 2.0 client metadata from a client configuration endpoint.
This method fetches the client configuration using the client's metadata URL.
It follows RFC 7591 (OAuth 2.0 Dynamic Client Registration) and RFC 8414 (OAuth 2.0 Authorization Server Metadata) specifications for client metadata discovery.
clientId The URL where the client's metadata can be retrieved.
This must be a valid absolute http(s) URI that returns a JSON response containing the client configuration.
client Optional HTTP client, mainly for testing. When omitted, a
default client is used.
Returns a Future<OAuthClientMetadata> containing the parsed client configuration including redirect URIs, grant types, token endpoint auth method, and other OAuth-specific settings.
Throws:
- ArgumentError in the following cases:
- When clientId is empty
- When clientId is not a valid absolute https URI (
http://is only accepted for loopback hosts —localhost,127.0.0.1,[::1]— as the development exception allowed by the atproto OAuth spec)
- OAuthException when:
- The HTTP request fails
- The server returns a non-200 status code
- The response cannot be parsed as valid client metadata
Example:
final metadata = await oauth.getClientMetadata(
'https://atprotodart.com/oauth/bluesky/atprotodart/client-metadata.json'
);
print('Allowed redirect URIs: ${metadata.redirectUris}');
The returned OAuthClientMetadata typically includes:
- Client identifier
- Client authentication methods
- Authorized redirect URIs
- Allowed grant types
- Client name and description
- Client URI and logo URI
- Contacts
- Scope restrictions
- Other client-specific configuration
Note: The endpoint should be accessed over HTTPS to ensure secure transmission of client configuration data.
Implementation
Future<OAuthClientMetadata> getClientMetadata(
final String clientId, {
final http.Client? client,
}) async {
if (clientId.isEmpty) {
throw ArgumentError.value(clientId, 'clientId', 'must not be empty');
}
// The atproto OAuth spec restricts `client_id` to `https://` URLs, with a
// development-only exception for `http://` on loopback hosts.
final uri = Uri.tryParse(clientId);
if (uri == null ||
!uri.isAbsolute ||
!(uri.isScheme('https') ||
(uri.isScheme('http') && _isLoopbackHost(uri.host)))) {
throw ArgumentError.value(
clientId,
'clientId',
'must be a valid absolute https URI '
'(http is only allowed for localhost)',
);
}
final response = client == null ? await http.get(uri) : await client.get(uri);
if (response.statusCode != 200) {
throw OAuthException(
'Failed to get client metadata: ${response.statusCode}',
);
}
final json = _tryDecodeJsonMap(response.body);
if (json == null) {
throw OAuthException(
'Failed to parse client metadata: response is not a JSON object',
);
}
return OAuthClientMetadata.fromJson(json);
}