exchangeCodeForTokens function

Future<OAuthTokenExchangeResponse> exchangeCodeForTokens({
  1. required String authorizationCode,
  2. required String state,
  3. required String codeVerifier,
  4. required int port,
  5. bool useManualRedirect = false,
  6. int? expiresIn,
  7. HttpClient? httpClient,
})

Exchange authorization code for tokens.

Implementation

Future<OAuthTokenExchangeResponse> exchangeCodeForTokens({
  required String authorizationCode,
  required String state,
  required String codeVerifier,
  required int port,
  bool useManualRedirect = false,
  int? expiresIn,
  HttpClient? httpClient,
}) async {
  final config = getOauthConfig();
  final body = <String, dynamic>{
    'grant_type': 'authorization_code',
    'code': authorizationCode,
    'redirect_uri': useManualRedirect
        ? config.manualRedirectUrl
        : 'http://localhost:$port/callback',
    'client_id': config.clientId,
    'code_verifier': codeVerifier,
    'state': state,
  };

  if (expiresIn != null) body['expires_in'] = expiresIn;

  final client = httpClient ?? HttpClient();
  try {
    final request = await client.postUrl(Uri.parse(config.tokenUrl));
    request.headers.contentType = ContentType.json;
    request.write(jsonEncode(body));
    final response = await request.close().timeout(const Duration(seconds: 15));

    final responseBody = await response.transform(utf8.decoder).join();

    if (response.statusCode != 200) {
      throw Exception(
        response.statusCode == 401
            ? 'Authentication failed: Invalid authorization code'
            : 'Token exchange failed (${response.statusCode}): $responseBody',
      );
    }

    return OAuthTokenExchangeResponse.fromJson(
      jsonDecode(responseBody) as Map<String, dynamic>,
    );
  } finally {
    if (httpClient == null) client.close();
  }
}