I am trying to build an app with flutter that uses Fitbit API, I tried different packages to do Web Authentication like Fitbitter that uses flutter-web-auth for authentication. Also tried web-view Widget.
in case of Fitbitter :
the issue is when I logged in the fitbit account and get the response that content authorization code https://example.com/callback?code=<authorization_code>#_=_0.
authorize method in FitbitConnector class doesn't redirect me back to the app with authorization code instead stays in the chrome custom tab.
authorize method
static Future<String?> authorize(
{BuildContext? context,
String? clientID,
String? clientSecret,
required String redirectUri,
required String callbackUrlScheme}) async {
// Instantiate Dio and its Response
Dio dio = Dio();
Response response;
String? userID;
// Generate the fitbit url
final fitbitAuthorizeFormUrl = FitbitAuthAPIURL.authorizeForm(
userID: userID, redirectUri: redirectUri, clientID: clientID);
// Perform authentication
try {
final result = await FlutterWebAuth.authenticate(
url: fitbitAuthorizeFormUrl.url!,
callbackUrlScheme: callbackUrlScheme);
//Get the auth code
final code = Uri.parse(result).queryParameters['code'];
// Generate the fitbit url
final fitbitAuthorizeUrl = FitbitAuthAPIURL.authorize(
userID: userID,
redirectUri: redirectUri,
code: code,
clientID: clientID,
clientSecret: clientSecret);
response = await dio.post(
fitbitAuthorizeUrl.url!,
data: fitbitAuthorizeUrl.data,
options: Options(
contentType: Headers.formUrlEncodedContentType,
headers: {
'Authorization': fitbitAuthorizeUrl.authorizationHeader,
},
),
);
// Debugging
final logger = Logger();
logger.i('$response');
// Save authorization tokens
final accessToken = response.data['access_token'] as String;
final refreshToken = response.data['refresh_token'] as String;
userID = response.data['user_id'] as String?;
GetIt.instance<SharedPreferences>()
.setString('fitbitAccessToken', accessToken);
GetIt.instance<SharedPreferences>()
.setString('fitbitRefreshToken', refreshToken);
} catch (e) {
print(e);
} // catch
return userID;
}
Do you know a way to do web authentication and get redirected to the app with user Token and ID?
Related
I'm making a flutter app using Spotify's API. I have a basic homepage that uses a button to launch a browser to login to Spotify. Here is my backend code:
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:uni_links/uni_links.dart';
class SpotifyAuth with ChangeNotifier {
final String CLIENT_ID = "My client ID";
final String ClIENT_SECRET = "My client secret";
final String REDIRECT_URI = "http://localhost:8000/callback";
final String SCOPE = 'user-read-private user-read-email';
// var state = 'your-state';
late String _accessToken;
late String _refreshToken;
Uri createAuthenticationUri(){
var query = [
'response_type=code',
'client_id=$CLIENT_ID',
'scope=${Uri.encodeComponent(SCOPE)}',
'redirect_uri=${Uri.encodeComponent(REDIRECT_URI)}',
];
var queryString = query.join('&');
var url = 'https://accounts.spotify.com/authorize?' + queryString;
var parsedUrl = Uri.parse(url);
return parsedUrl;
}
Future<void> launchInBrowser() async {
if (!await launchUrl(
createAuthenticationUri(),
mode: LaunchMode.externalApplication,
)){
throw Exception('Could not launch Url');
}
}
Future<void> launchAuth() async {
await launchInBrowser();
await initUniLinks();
}
Future<void> getAccessToken(String code) async {
var body = {
"grant_type": "authorization_code",
"code": code,
"redirect_uri": REDIRECT_URI,
"client_id": CLIENT_ID,
"client_secret": ClIENT_SECRET
};
// Create a request header with the required information
var header = {
"Content-Type": "application/x-www-form-urlencoded",
"Authorization":
"Basic ${base64Encode(utf8.encode("$CLIENT_ID:$ClIENT_SECRET>"))}"
};
// Send the request to the Spotify token endpoint
var response = await http.post(
Uri.parse("https://accounts.spotify.com/api/token"),
body: body,
headers: header);
// Check if the request was successful
if (response.statusCode == 200) {
// Parse the JSON response
var data = json.decode(response.body);
// Get the access token from the response
String accessToken = data["access_token"];
// Store the access token for future use
// ...
_accessToken = accessToken;
} else {
print("Error");
}
}
Future<void> initUniLinks() async {
// Get the latest initial link
String? initialLink = await getInitialLink();
// Check if the link contains a Spotify authorization code
if (initialLink != null && initialLink.contains("code=")) {
// Extract the code from the link
String code = initialLink.split("code=")[1];
// Use the code to get an access token from Spotify
getAccessToken(code);
}
else{
print("Nothing");
}
}
}
My redirect URI is set in the spotify dashboard.
My app widget calls luanchAuth();
and then it should wait for the authentication code with initUniLinks() but it seems like initUniLinks() executes immediately without waiting for the authentication. When I authenticate in Spotify, it throws a generic "can't connect to localhost" error page but the url includes the auth code that I need.
Not sure what I'm doing wrong here. Admittedly I'm new to Oauth and app-api-connections in general but I thought this would work.
REDIRECT URI is the problem here, You cannot have redirect URI with localhost it fails. either use ngrok and provide the mapped https url or host your callback url and provide it.
Use the custom scheme for redirect_uri, something like this my-app://token/callback. See App Settings for Spotify rules.
Then configure the application for Deep Linking to receive the authentication response.
I am using DIO package for API request but the issue is that when I request for another API while the first API is still in progress.
It doesn't cancel the first request. Both the APIs run simultaneously which is not the desired in my app scenario.
class DioClient {
static BaseOptions options = BaseOptions(baseUrl: baseUrl);
Dio _dio = Dio(options);
Future<dynamic> postFormData(
{dynamic data, String url, dynamic header}) async {
final data1 = data;
var formData = FormData.fromMap(data1);
try {
var response = await _dio.post(url,
options: Options(headers: header), data: formData);
return response.data;
} catch (e) {
throw e;
}}}
If you want to cancel the API request call then you need to use the cancel token provided by DIO.
You need to pass cancel token in dio request when you make other API call use that cancel token to cancel the API request
Here is the code
class DioClient {
static BaseOptions options = BaseOptions(baseUrl: baseUrl);
//Here is line you need
CancelToken cancelToken=CancelToken();
Dio _dio = Dio(options);
Future<dynamic> postFormData(
{dynamic data, String url, dynamic header}) async {
final data1 = data;
var formData = FormData.fromMap(data1);
try {
//pass cancel token here
var response = await _dio.post(url,
options: Options(headers: header), data: formData,cancelToken: cancelToken);
return response.data;
} catch (e) {
throw e;
}}}
And use that cancelToken to cancel the API request when you call another API first you cancel the previous request.
cancelToken.cancel();
Enjoy!
I am in the learning proess for both flutter and requests so forgive me if it is a simple mistake. I am trying to make a client login to a mediaiwki instance using client login api. I can fetch the login token succesfully but when I try to login it says invalid csrf token it gives {"error":{"code":"badtoken","info":"Invalid CSRF token.","*":". The api I am using to login is as follows.
https://www.mediawiki.org/wiki/API:Login#Method_2._clientlogin
Thanks for your help.
To fetch login token succesfully I use
_fetch_login_token() async {
Map<String, String> headers = {"Content-type": "application/json"};
Map<String, String> body = {
'action': "query",
'meta': "tokens",
'type': "login",
'format': "json"
};
Response response = await post(
url,
body: body,
);
//print(response);
// int statusCode = response.statusCode;
// print(statusCode);
var decoded = jsonDecode(response.body);
print(decoded);
var jsonsData = response.body; // toString of Response's body is assigned to jsonDataString
var data = jsonDecode(jsonsData);
var token=data['query']['tokens']['logintoken'];
return _makePostRequest(token);
}
And my failed login as follows
Map<String, String> body = {
'action': "clientlogin",
'username': username,
'password': password,
'loginreturnurl': url,
'logintoken': loginToken,
'format': "json"
};
Response response = await post(
url,
body:body,
);
I solved the problem.For future reference, I have downloaded dio package and added intercepter and cookie manager in order to persist the cookies.
I have a Flutter app that I'm trying to integrate with Pinterest, and I'm a little stuck.
I have code to request an access token and, while the code does get an access token, that token does not appear to be useful. Any API that I call with that token results in a 308, and if I go to the Pinterest developer site and debug the token, then it looks like this:
So, it's like the token has no scopes and was not issued for an actual application, which is very weird. The code I have looks like this:
Future<String> _login() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
accessToken = null;
if (accessToken == null) {
// accessToken = prefs.get(ACCESS_TOKEN_KEY);
//If we don't have an existing access token, get a new one.
if (accessToken == null) {
final appId = "myappid";
final secret =
"mysecret";
final url = Uri.https('api.pinterest.com', 'oauth', {
'response_type': 'code',
'client_id': appId,
'redirect_uri': "pdk<myappid>://",
'state': 'someBogusStuff',
'scope': 'read_public,write_public',
});
final result = await FlutterWebAuth.authenticate(
url: url.toString(), callbackUrlScheme: 'pdk<myappid>');
print(result);
final tokenEndpoint = Uri.https('api.pinterest.com', 'v1/oauth/token');
// Use the code to get an access token
final response = await http.post(tokenEndpoint, body: {
'client_id': appId,
'client_secret': secret,
'grant_type': 'authorization_code',
'code': Uri
.parse(result)
.queryParameters['code'],
});
if (response.statusCode != 200) {
return response.body;
}
var decodedResponse = jsonDecode(response.body);
print(decodedResponse);
accessToken = decodedResponse['access_token'];
//Save the access token
prefs.setString(ACCESS_TOKEN_KEY, accessToken);
}
}
return getMe(accessToken);
}
Future<String> getMe(String token) async {
final url =
Uri.https('api.pinterest.com', 'v1/me', {'access_token': token});
Completer<String> completer = Completer();
String result;
http.get(url, headers: {'User-Agent': 'PDK 1.0'}).then((response) {
print(response.statusCode);
result = response.body;
}).whenComplete(() => completer.complete(result));
return completer.future;
}
When I print out the result of the call to /oauth/token it looks like I got back a good token:
{access_token: AvtF3MxUy4gbujGGhN_KcYFExQVAFfmOZGmxYN5GkhE-iKDH6QpYADAAAzbHRpc4dD1gvFwAAAAA, token_type: bearer, scope: [read_write_all, read_public, write_public, read_private, write_private]}
But it doesn't work. What am I doing wrong here?
I'd like to get the auth token from firebase (email and password auth) to authenticate in my firebase cloud function. It seems like the functions getIdToken() and getToken() are both not working for firebase_auth package.
is there an other function or is there even a better idea to make sure only authenticated users can trigger the cloud functions?
var token = await FirebaseAuth.instance.currentUser.getIdToken();
var response = await httpClient.get(url,headers: {'Authorization':"Bearer $token"});
I agree with #Doug on this one - callable wraps this for you and will be easier -, but my use case required me to make HTTPS calls (onRequest in Functions). Also, I think you're just in the correct path - but you're possibly not checking it in your Cloud Functions.
In your app, you'll call:
_httpsCall() async {
// Fetch the currentUser, and then get its id token
final user = await FirebaseAuth.instance.currentUser();
final idToken = await user.getIdToken();
final token = idToken.token;
// Create authorization header
final header = { "authorization": 'Bearer $token' };
get("http://YOUR_PROJECT_BASE_URL/httpsFunction", headers: header)
.then((response) {
final status = response.statusCode;
print('STATUS CODE: $status');
})
.catchError((e) {
print(e);
});
}
In your function, you'll check for the token:
export const httpsFunction = functions.https.onRequest((request, response) => {
const authorization = request.header("authorization")
if (authorization) {
const idToken = authorization.split('Bearer ')[1]
if (!idToken) {
response.status(400).send({ response: "Unauthenticated request!" })
return
}
return admin.auth().verifyIdToken(idToken)
.then(decodedToken => {
// You can check for your custom claims here as well
response.status(200).send({ response: "Authenticated request!" })
})
.catch(err => {
response.status(400).send({ response: "Unauthenticated request!" })
})
}
response.status(400).send({ response: "Unauthenticated request!" })
})
Keep in mind:
If I'm not mistaken, those tokens are valid for 1 hour, if you are going to store them somewhere, just be aware of this. I've tested locally and it takes around 200~500ms - every time - to get only the id token, which in most cases are not that big of overhead - but is significant.
It's going to be easiest for you to use a callable function, since that lets you:
Automatically send the current user's uid in the request.
Know very easily on the function side if a UID was provided in the request, and refuse service if none was provided.
The flutter plugin is here.
You should be able to do the equivalent work yourself, though, since callable functions are just a wrapper around normal HTTP connections. It's possible for you to get the ID token of the logged in user.
import 'package:firebase_messaging/firebase_messaging.dart';
.
.
.
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
#override
Future<void> initState() {
super.initState();
_firebaseMessaging.getToken().then((token) {
assert(token != null);
print("teken is: " + token);
});
}
Get your token from firebaseAuth and put in a string.
Future<Details> getDetails() async {
String bearer = await FirebaseAuth.instance.currentUser!.getIdToken();
print("Bearer: " + bearer.toString());
String token = "Bearer ${bearer}";
var apiUrl = Uri.parse('Your url here');
final response = await http.get(apiUrl, headers: {
'Authorization' : '${token}'
});
final responseJson = jsonDecode(response.body);
return Details.fromJson(responseJson);
}