Unable to Retrieve Access Token Using Implicit Grant Flow in Flutter App

I’m currently working on a Flutter desktop application for Windows and trying to integrate the Twitch API using the Implicit Grant Flow. However, I’m unable to retrieve the access_token after the user authorizes the application. I understand that the token is returned in the fragment of the redirect URI (#access_token=), and the server should not process or know the token. Despite this, I can’t seem to capture the token correctly in my app.

Here’s a summary of my setup and what I’ve tried so far:


1. OAuth Flow Setup
I’m using the following URL to initiate the OAuth flow:

static Future<void> logInTwitch(BuildContext context) async {
  final Uri authUrl = Uri.parse(
      'https://id.twitch.tv/oauth2/authorize'
      '?response_type=token'
      '&client_id=$clientId'
      '&redirect_uri=$redirectUri'
      '&scope=$scopes'
      '&state=unique_state_string');

  if (await canLaunchUrl(authUrl)) {
    await launchUrl(authUrl, mode: LaunchMode.externalApplication);
  } else {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('Failed to open authentication URL')),
    );
  }
}

Parameters:

  • client_id: My registered client ID.
  • redirect_uri: (configured in Twitch Developer Console).
  • response_type: token.
  • scope: chat:read chat:edit user:read:email.
  • state: A unique string for CSRF protection.

2. Redirect URI Handling

The redirect_uri points to my local Node.js server, which simply closes the browser tab after redirection. Here’s the server code:

const express = require('express');
const https = require('https');
const fs = require('fs');
const path = require('path');

const app = express();

// Handle Twitch redirection
app.get('/auth/twitch', (req, res) => {
    res.send('<script>window.close();</script>');
});

// Load SSL certificates
const sslOptions = {
    key: fs.readFileSync(path.join(__dirname, '../certificates/private.key')),
    cert: fs.readFileSync(path.join(__dirname, '../certificates/certificate.crt')),
    ca: fs.readFileSync(path.join(__dirname, '../certificates/ca_bundle.crt')),
};

// Start HTTPS server
const PORT = 8443;
https.createServer(sslOptions, app).listen(PORT, () => {
    console.log(`Server running at <myURL>:${PORT}`); (This is correct)
});

3. Capturing the Access Token

I’m using the uni_links package in Flutter to capture the redirect URI and extract the token from the fragment. Here’s my implementation:

Listening for Redirects

void initUniLinks(BuildContext context) {
  uriLinkStream.listen((Uri? uri) {
    if (uri != null && uri.fragment.contains('access_token')) {
      ConfiguracionMethods.handleRedirectUrl(context, uri.toString());
    }
  });
}

Processing the Redirect URI

static Future<void> handleRedirectUrl(BuildContext context, String url) async {
  try {
    final uri = Uri.parse(url);
    final fragment = uri.fragment; // Extract the fragment
    final params = Uri.splitQueryString(fragment);
    final accessToken = params['access_token'];

    if (accessToken != null) {
      final configBox = Hive.box('app_config');
      await configBox.put('twitchAccessToken', accessToken);

      if (context.mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('Twitch account connected')),
        );
      }

      print('Access token: $accessToken');
    } else {
      throw 'Access token not found in the URL fragment';
    }
  } catch (e) {
    if (context.mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Error handling redirect: $e')),
      );
    }
  }
}

Despite the above setup, I’m unable to retrieve the access_token . The browser redirects to the correct redirect_uri , but the token is not captured in my Flutter app. I suspect the issue might be related to how the uni_links package handles the fragment portion of the URL or how the browser interacts with the app.

Any guidance or suggestions would be greatly appreciated. Thank you! :blush:

Use device code flow instead.

Then your flutter app doesn’t need to start a HTTPS server, let alone run self signed certs.
Or need to do any schnanigans to capture a redirect.

Without duplicated your code/scenario and creating a flutter app to run myself to test, the problem is in how flutter is setup here, but can’t tell you where without building and running it myself.

The optimal solution: use DCF instead, removes all the “work arounds” you’ve put in to handle “regular” oAuth

at a guess this is failing as the input to Uri.splitQueryString starts # instead of ? when is treated as invalid by this code.

Thank you so much, indeed the solution was changing to DCF to simplify all the connection method as my app will be located on a stand alone device. :sweat_smile: