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!