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.

1 Like

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: