OAuth 2.0 verification problem in flutter

50 Views Asked by At

I'm using the webview_flutter package in my Flutter app to perform Google Fit authentication and retrieve heartbeat data. However, I'm encountering issues with verification because the requests made by the WebView don't comply with Google's policies.

My objective is to continuously fetch the user's heartbeat data after obtaining access. Is there an alternative approach I can use to achieve this, considering the limitations with webview_flutter?

Here's a summary of my current setup:

handle the Google Fit authentication process. Attempting to retrieve heartbeat data continuously after obtaining access. I'd appreciate any suggestions or guidance on how to overcome this issue or alternative methods to achieve the same goal.

frontend

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:url_launcher/url_launcher.dart';
import 'package:webview_flutter/webview_flutter.dart';

class ConnectSmartwatch extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Google Fit API Demo')),
        body: Center(
          child: ElevatedButton(
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute(builder: (context) => HeartRatePage()),
              );
            },
            child: Text('Get Heart Rate Data'),
          ),
        ),
      ),
    );
  }
}

class HeartRatePage extends StatefulWidget {
  @override
  _HeartRatePageState createState() => _HeartRatePageState();
}

class _HeartRatePageState extends State<HeartRatePage> {
  String _heartRateData = '';

  @override
  void initState() {
    super.initState();
    initiateAuthentication(); // Call this when a specific action is performed
  }

  void initiateAuthentication() async {
    try {
      final url = await _getAuthUrl(); // Fetch authentication URL from backend
      Navigator.push(
        context,
        MaterialPageRoute(builder: (context) => WebViewPage(url, exchangeCodeForTokens)),
      );
    } catch (e) {
      print('Failed to initiate authentication: $e');
      throw Exception('Failed to initiate authentication: $e');
    }
  }

  Future<String> _getAuthUrl() async {
    try {
      final response = await http.get(Uri.parse('https://your-backend-url.com/getAuthUrl'));

      if (response.statusCode == 200) {
        final url = jsonDecode(response.body)['url'];
        return url;
      } else {
        throw Exception('Failed to load auth URL');
      }
    } catch (e) {
      print('Failed to get auth URL: $e');
      throw Exception('Failed to fetch authentication URL: $e');
    }
  }

  void exchangeCodeForTokens(String? code) async {
    if (code != null) {
      final response = await http.get(Uri.parse('https://your-backend-url.com/exchangeCode?code=$code'));

      if (response.statusCode == 200) {
        final tokens = jsonDecode(response.body);
        _getHeartRateData(tokens);
      } else {
        throw Exception('Failed to exchange code for tokens');
      }
    }
  }

  Future<void> _getHeartRateData(Map<String, dynamic> tokens) async {
    final String accessToken = tokens['access_token'];
    final response = await http.get(Uri.parse('https://www.googleapis.com/fitness/v1/users/me/dataset:aggregate'),
      headers: {
        'Authorization': 'Bearer $accessToken',
      },
    );

    if (response.statusCode == 200) {
      setState(() {
        _heartRateData = jsonDecode(response.body);
      });
    } else {
      throw Exception('Failed to load heart rate data');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Heart Rate Data')),
      body: Center(
        child: Text('Heart Rate Data: $_heartRateData'),
      ),
    );
  }
}

class WebViewPage extends StatelessWidget {
  final String url;
  final Function(String?) onCodeReceived;

  WebViewPage(this.url, this.onCodeReceived);

  void launchAuthUrlAgain() async {
    if (await canLaunch(url)) {
      await launch(url);
    } else {
      print('Could not launch URL');
      throw 'Could not launch $url';
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Auth Page')),
      body: WebView(
        initialUrl: url,
        javascriptMode: JavascriptMode.unrestricted,
        onPageFinished: (String url) {
          if (url.startsWith('https://your-backend-url.com/redirectPage')) {
            var uri = Uri.parse(url);
            var code = uri.queryParameters['code'];
            launchAuthUrlAgain();
            onCodeReceived(code);
          }
        },
      ),
    );
  }
}

backend

import queryParse from 'query-string';
import express from 'express';
import { google } from 'googleapis';
import bodyParser from 'body-parser';
import axios from 'axios';

const app = express();
const port = 3000;

app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());

app.get("/getAuthUrl", async (req, res) => {
    try {
        // Your authentication logic here
        const oauth2Client = new google.auth.OAuth2(
            "YOUR_CLIENT_ID",
            "YOUR_CLIENT_SECRET",
            "YOUR_REDIRECT_URI"
        );

        const scopes = ["https://www.googleapis.com/auth/fitness.heart_rate.read"];
        const url = oauth2Client.generateAuthUrl({
            access_type: 'offline',
            scope: scopes,
            redirect_uri: "YOUR_REDIRECT_URI",
            state: JSON.stringify({
                callbackURL: req.body.callbackURL,
                userID: req.body.userid
            })
        });

        res.send({url});
    } catch (e) {
        console.log('Failed to generate auth URL:', e);
        res.status(500).send("Internal Server Error");
    }
});

app.get("/exchangeCode", async (req, res) => {
    try {
        const queryURL = new urlParse(req.url);
        const parsedQuery = queryParse.parse(queryURL.query);
        if (!parsedQuery.code) {
            console.log('No code in query parameters');
            return res.status(400).send('Bad Request: No code in query parameters');
        }
        const code = parsedQuery.code;

        const oauth2Client = new google.auth.OAuth2(
            "YOUR_CLIENT_ID",
            "YOUR_CLIENT_SECRET",
            "YOUR_REDIRECT_URI"
        );

        const tokens = await oauth2Client.getToken(code);

        res.send(tokens);
    } catch (e) {
        console.log('Failed to exchange code for tokens:', e);
        res.status(500).send("Internal Server Error");
    }
});

app.listen(port, () => console.log(`Server is listening on port ${port}!`));

0

There are 0 best solutions below