Flutter oAuth2 login with discord. Error with redirect URI

1.3k views Asked by At

I'm looking to make a button login with discord. For that I use flutter_web_auth but discord shows me an error with the redirect URI.

Invalid OAuth2 redirect_uri

Redirect URI is not supported by client

config discord

I set up flutter_web_auth as requested:

AndroidManifest.xml

       <activity android:name="com.linusu.flutter_web_auth.CallbackActivity" >
           <intent-filter android:label="flutter_web_auth">
               <action android:name="android.intent.action.VIEW" />
               <category android:name="android.intent.category.DEFAULT" />
               <category android:name="android.intent.category.BROWSABLE" />
               <data android:scheme="com.area" />
           </intent-filter>
       </activity>

function

void loginWithDiscord() async {

// App specific variables

    const clientId = 'myClientId' ;
    const callbackUrlScheme = 'com.area';
    const redirectUri = 'com.area://home'; // OR 'com.area:/';

// Construct the url

    final url = Uri.https('discord.com', '/api/oauth2/authorize', {
      'response_type': 'code',
      'client_id': clientId,
      'redirect_uri': redirectUri,
      'scope': 'identify',
    });

// Present the dialog to the user

    final result = await FlutterWebAuth.authenticate(
        url: url.toString(), callbackUrlScheme: callbackUrlScheme);

// Extract code from resulting url

    final code = Uri.parse(result).queryParameters['code'];

// Use this code to get an access token

    final response = await http
        .post(Uri.parse('https://discord.com/api/oauth2/authorize'), body: {
      'client_id': clientId,
      'redirect_uri': redirectUri,
      'grant_type': 'authorization_code',
      'code': code,
    });

// Get the access token from the response

    final accessToken = jsonDecode(response.body)['access_token'] as String;
    print(accessToken);
  }
1

There are 1 answers

0
Pillou On

Your issue is similar with this one (https://github.com/discord/discord-api-docs/issues/5106) :

Discord OAuth2 with mobile require PCKE (Proof Key for Code Exchange) : https://datatracker.ietf.org/doc/html/rfc7636

Proof Key for Code Exchange is an extension to the authorization code flow to prevent CSRF and authorization code injection attacks. The technique involves the client first creating a secret on each authorization request, and then using that secret again when exchanging the authorization code for an access token. This way if the code is intercepted, it will not be useful since the token request relies on the initial secret. (https://www.oauth.com/oauth2-servers/pkce/)

In your case, you need to set up a code_verifier and a code_challenge, the code_challenge will be sent in the authorize request with a code challenge method.

After you get the authorization_code, you will send a request to the token endpoint, you need to use the code_verifier at this moment.

Example :

code_verifier = high-entropy cryptographic random STRING using the unreserved characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~" from Section 2.3 of [RFC3986], with a minimum length of 43 characters and a maximum length of 128 characters.

method to generate a code_verifier

String generateCodeVerifier() {
    const String _charset =
        'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
    return List.generate(
        128, (i) => _charset[Random.secure().nextInt(_charset.length)]).join();
}

After generating the code_verifier, you need to generate the code_challenge from the code_verifier.

String generateCodeChallenge(String codeVerifier) {
    var bytes = ascii.encode(codeVerifier);
    var digest = sha256.convert(bytes);
    String codeChallenge = base64Url
        .encode(digest.bytes)
        .replaceAll("=", "")
        .replaceAll("+", "-")
        .replaceAll("/", "_");
    return codeChallenge;
  }

The S256 method computes the SHA-256 hash of the input and then encodes the hash value using Base64-URL. Then for this example, the code_challenge_method is S256.

Now, you're ready to use the Discord OAuth2 :

final _clientId = "CLIENT_ID";
final _clientSecret = "CLIENT_SECRET";
final _redirectUrl = "com.area://home"; // The one you set on the Discord Portal Developer
final _customUriScheme = "com.area";
late String _codeVerifier;
late String _codeChallenge;

@override
  void initState() {
    super.initState();
    _codeVerifier = generateCodeVerifier();
    _codeChallenge = generateCodeChallenge(_codeVerifier);
  }

void _loginWithDiscord() async {
    // First, get the authorization code, you need to use the code_challenge and code_challenge_method at this moment
    final url = Uri.https('discordapp.com', '/api/oauth2/authorize', {
      'response_type': 'code',
      "redirect_uri": _redirectUrl,
      'client_id': _clientId,
      'grant_type': 'authorization_code',
      'scope': 'identify',
      'code_challenge': _codeChallenge,
      'code_challenge_method': 'S256',

    final result = await FlutterWebAuth.authenticate(
        url: url.toString(), callbackUrlScheme: _customUriScheme);

    // get the code
    var code = result.split("code=")[2].split("&")[0];
    
    // Now that you have the authorization_code, get the TOKEN with a post request, you need to use the code_verifier at this moment
    final url2 = Uri.https('discordapp.com', '/api/oauth2/token');
    final response = await http.post(
      url2,
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: {
        "grant_type": "authorization_code",
        "code": code,
        "redirect_uri": _redirectUrl,
        "client_id": _clientId,
        "client_secret": _clientSecret,
        "code_verifier": _codeVerifier
      },
    );

    // if the request is a sucess, get the access token :
    if(response.statusCode == 200) {
         var jsonResponse = convert.jsonDecode(response.body) as Map<String, dynamic>;
         final accessToken = jsonResponse["access_token"];
         // your code
    } else {
      // if the request failed
      print(response.statusCode);
      print(response.body);
    }

});```