Flutter Spotify OAuth integration causing user cancellation error

I’m having trouble with Spotify login in my Flutter project

I’m building a music app and trying to connect it with Spotify’s web API. The login flow seems to work at first but then fails with an error.

When I tap the login button, everything looks good. The Spotify login screen opens and I can type my username and password. But right after I hit the login button, I get this error message:

PlatformException(CANCELED, User canceled login, null, null)

The weird thing is that the user gets sent back to my app after this happens. I’m using FlutterWebAuth2 for the authentication process.

Has anyone run into this before? I’m not sure what’s causing it.

Here’s my authentication setup:

import 'dart:convert';
import 'dart:math';
import 'package:crypto/crypto.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';

class MusicAuthService {
  final String appId;
  final String callbackUrl;
  final List<String> permissions;

  MusicAuthService({
    required this.appId,
    required this.callbackUrl,
    this.permissions = const [
      'user-read-playback-state',
      'user-modify-playback-state',
      'user-read-currently-playing',
      'playlist-read-private',
      'playlist-modify-public',
      'user-library-read',
      'user-follow-read',
      'user-top-read',
    ],
  });

  Future<String> login() async {
    final verifier = _createVerifier();
    final challenge = _createChallenge(verifier);

    final loginUrl = Uri.https('accounts.spotify.com', '/authorize', {
      'client_id': appId,
      'response_type': 'code',
      'redirect_uri': callbackUrl,
      'code_challenge_method': 'S256',
      'code_challenge': challenge,
      'scope': permissions.join(' '),
    });

    // Error happens here
    final authResult = await FlutterWebAuth2.authenticate(
      url: loginUrl.toString(),
      callbackUrlScheme: Uri.parse(callbackUrl).scheme,
    );

    final resultUri = Uri.parse(authResult);
    final authCode = resultUri.queryParameters['code'];
    if (authCode == null) {
      throw Exception("Could not get authorization code");
    }

    final tokenRequest = await http.post(
      Uri.parse('https://accounts.spotify.com/api/token'),
      headers: {'Content-Type': 'application/x-www-form-urlencoded'},
      body: {
        'client_id': appId,
        'grant_type': 'authorization_code',
        'code': authCode,
        'redirect_uri': callbackUrl,
        'code_verifier': verifier,
      },
    );

    if (tokenRequest.statusCode != 200) {
      throw Exception("Failed to get token: ${tokenRequest.body}");
    }

    final responseData = jsonDecode(tokenRequest.body);
    final token = responseData['access_token'];
    if (token == null) {
      throw Exception("Token not found in response");
    }

    return token;
  }

  String _createVerifier() {
    final random = Random.secure();
    final codes = List<int>.generate(64, (_) => random.nextInt(256));
    return base64UrlEncode(codes).replaceAll('=', '');
  }

  String _createChallenge(String verifier) {
    final encoded = utf8.encode(verifier);
    final hashed = sha256.convert(encoded);
    return base64UrlEncode(hashed.bytes).replaceAll('=', '');
  }
}

And here’s how I use it in my main app:

import 'package:flutter/material.dart';

void main() {
  runApp(MusicApp());
}

class MusicApp extends StatelessWidget {
  final MusicAuthService authService = MusicAuthService(
    appId: '****YOUR_APP_ID****',
    callbackUrl: 'myspotifyapp://auth',
  );

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Music Player')),
        body: Center(
          child: ElevatedButton(
            onPressed: () async {
              try {
                final accessToken = await authService.login();
                print('Got token: $accessToken');
              } catch (e) {
                print('Login failed: $e');
              }
            },
            child: Text('Connect Spotify Account'),
          ),
        ),
      ),
    );
  }
}

I’ve checked my Spotify app settings multiple times and the redirect URL matches what I have in the code. Any ideas what might be wrong?

check your pubspec.yaml dependencies first - i had the same issue too, and flutter_web_auth_2 was conflicting with other packages. i downgraded to 2.1.5 and it worked fine. also, test on a real device, emulators can mess up auth flows sometimes.

Had this exact problem six months ago with Spotify OAuth. FlutterWebAuth2 was choking on the callback URL scheme on some devices. Turned out I hadn’t registered the custom URL scheme properly in my config files. Check your android/app/src/main/AndroidManifest.xml - make sure the scheme’s in the activity tag. Same for ios/Runner/Info.plist on iOS. That’s what broke my auth flow right after Spotify’s login page. Also double-check your Spotify app dashboard has the exact redirect URI whitelisted. Even tiny differences like trailing slashes will trigger that cancellation error. Start with a simple callback URL to narrow down what’s breaking.

I’ve hit this exact issue with Spotify’s OAuth flow. That cancellation error usually happens when the callback redirect fires too fast for FlutterWebAuth2 to catch it properly. Spotify sometimes throws in an extra redirect that messes with the auth listener.

Couple things to try: Add a small delay in your auth flow, or switch to a different callback URL pattern. Drop the custom scheme and use something like ‘http://localhost:8080/callback’ for dev work - you’ll need to update your Spotify app settings and Flutter code to match.

Also, wrap your FlutterWebAuth2.authenticate call in try-catch and check if the error actually has a valid callback URL buried in it. I’ve seen cases where the auth actually works but still throws that cancellation error because of URL parsing weirdness.

Your code looks solid, but this error usually happens when there’s a mismatch between what Spotify expects and what your app gets during the redirect. Spotify finishes the auth on their end, but FlutterWebAuth2 thinks it got cancelled.

I hit this same issue with a custom scheme that wasn’t set up right across platforms. Fixed it by switching to a universal link instead. Change your callback URL to something like ‘https://yourapp.com/auth’ and set it up as a universal link in your app config. Don’t forget to update your Spotify app dashboard with the new URL.

Also check your Flutter channel - I had similar problems on beta that went away when I switched back to stable. OAuth flows are pretty sensitive to Flutter engine changes.

This cancellation error is classic OAuth redirect timing chaos. I’ve dealt with this nightmare across multiple projects and manual debugging gets old fast.

Spotify’s auth flow has weird timing quirks where the redirect happens before FlutterWebAuth2 can capture it. Instead of wrestling with callback URLs and manifest files, I just automate the entire OAuth dance now.

Set up a simple webhook endpoint that handles the Spotify callback. Use an automation platform to orchestrate the whole flow - capture the auth code, exchange it for tokens, send the result back to your app through a clean API call. No more platform-specific URL scheme headaches.

I do this for all our mobile apps now. The automation handles edge cases like double redirects, timeouts, and token refresh cycles without any Flutter workarounds. Your app just makes a simple HTTP request and gets back clean tokens.

This saved me weeks of debugging OAuth edge cases across different devices and OS versions. Way more reliable than trying to catch redirects in Flutter.

Check out Latenode for setting this up. It handles webhook automation perfectly and you can have this running in 20 minutes: https://latenode.com