exchangeCodeForToken method

  1. @override
Future<OAuthToken> exchangeCodeForToken({
  1. required String code,
  2. required String codeVerifier,
  3. String? responseIssuer,
})
override

Exchange an authorization code for tokens. When the authorization response carried an iss parameter (RFC 9207), pass it as responseIssuer — it is validated against the AS issuer before the exchange (mix-up defense).

Implementation

@override
Future<OAuthToken> exchangeCodeForToken({
  required String code,
  required String codeVerifier,
  String? responseIssuer,
}) async {
  await validateAuthorizationResponseIssuer(responseIssuer);
  final metadata = await _discoverMetadata();

  final body = <String, String>{
    'grant_type': 'authorization_code',
    'code': code,
    'client_id': effectiveClientId,
    'code_verifier': codeVerifier,
    if (config.redirectUri != null) 'redirect_uri': config.redirectUri!,
  };

  final headers = <String, String>{
    'Content-Type': 'application/x-www-form-urlencoded',
    'Accept': 'application/json',
  };

  // Add client authentication if confidential client
  if (config.clientSecret != null) {
    final credentials = base64Encode(
      utf8.encode('${config.clientId}:${config.clientSecret}'),
    );
    headers['Authorization'] = 'Basic $credentials';
  }

  final response = await _httpClient.post(
    Uri.parse(metadata.tokenEndpoint),
    headers: headers,
    body: body.entries
        .map((e) => '${e.key}=${Uri.encodeComponent(e.value)}')
        .join('&'),
  );

  final json = jsonDecode(response.body) as Map<String, dynamic>;

  if (response.statusCode != 200) {
    throw OAuthError.fromJson(json);
  }

  return OAuthToken.fromJson(json);
}