Hi guys!
I'm building a Flutter app, and I want to implement logging in via a web browser within the app when the login button is clicked.
Here's the link: https://XXX.sandbox.my.site.com/services/oauth2/authorize?response_type=code&client_id=$clientId&redirect_uri=$redirectUri;
Upon logging in, I receive this URL as the redirect_uri: https://XXX.sandbox.my.site.com/services/oauth2/callback
and it provides me with this URL: https://XXX.sandbox.my.site.com/services/oauth2/callback?code=aPrxLwJ_Q1_93GLJYLcITEmaCP9l7ael258XF6M79BG_xRhdGb6YVNCCSc8G_.64kh3e3R.jww%3D%3D&sfdc_community_url=https://XXX.sandbox.my.site.com&sfdc_community_id=0DB7Q000001MvPYWA0
I want to extract the code parameter from this URL and store it locally for the subsequent process in my Flutter app.
Currently, I have this code, but when I log in and get redirected to the callback where I want to extract the code, nothing happens. Does anyone have any idea what I'm doing wrong or what I'm missing?
My login screen code:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_web_browser/flutter_web_browser.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:uni_links/uni_links.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({Key? key}) : super(key: key);
@override
_LoginScreenState createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
var _isLoading = false;
final String clientId = 'CLIENT_ID';
final String clientSecret = 'CLIENT_SECRET';
final String redirectUri = 'https://XXX.sandbox.my.site.com/services/oauth2/callback';
late StreamSubscription _sub;
@override
void initState() {
super.initState();
initUniLinks();
}
void initUniLinks() async {
await initPlatformState();
_sub = uriLinkStream.listen((Uri? uri) {
print('Got url: $uri');
if (uri != null && uri.queryParameters.containsKey('code')) {
String code = uri.queryParameters['code']!;
print('Get code: $code');
_getToken(code);
}
}, onError: (err) {
print('Error: $err');
});
}
Future<void> initPlatformState() async {
await Future.delayed(Duration(seconds: 1));
}
@override
void dispose() {
_sub.cancel();
super.dispose();
}
void _tryLogin() async {
setState(() {
_isLoading = true;
});
String authUrl =
'https://XXX.sandbox.my.site.com/services/oauth2/authorize?response_type=code&client_id=$clientId&redirect_uri=$redirectUri';
await FlutterWebBrowser.openWebPage(url: authUrl);
setState(() {
_isLoading = false;
});
}
Future<void> _getToken(String code) async {
final response = await http.post(
Uri.parse('https://XXX.sandbox.my.site.com/services/oauth2/token'),
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: {
'grant_type': 'authorization_code',
'code': code,
'client_id': clientId,
'client_secret': clientSecret,
'redirect_uri': redirectUri,
},
);
if (response.statusCode == 200) {
print('Response: ${response.body}');
final data = jsonDecode(response.body);
print('Access Token: ${data['access_token']}');
} else {
//TODO: Handle login failure
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Sign in'),
),
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (_isLoading)
const CircularProgressIndicator()
else
ElevatedButton(
onPressed: _tryLogin,
style: ElevatedButton.styleFrom(
minimumSize: const Size.fromHeight(50),
textStyle: const TextStyle(fontSize: 18),
),
child: const Text('Sign in'),
),
],
),
),
),
);
}
}
AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="app"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<!-- Deep Links -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="XXX--qa.sandbox.my.site.com" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
Thanks in advance!