Flutter oAuth : how to get started with OAuth and Stripe connect - flutter

I am trying to implement stripe connect in my flutter app. Here are the steps I need to implement. Can anyone please navigate me on how I could achieve this in Flutter?
I am able to create a button with the endpointUrl but that's all..
Thanks

I found out this myself using firebase cloud functions:
first you create an https function in the firebase cloud function
then you add the link created by the function to your stripe dashboard
then you write the following logic to your function
obtain the the authorisation code
fetch data from stripe
save the response somewhere (in my case in realtime database)
Here is the function
exports.connectStripeStandardAccount = functions.https.onRequest((req, res) => {
let authCode = req.query.code;
return stripe.oauth.token({
grant_type: 'authorization_code',
code: authCode,
}).then(async response => {
await admin.database()
.ref(`/accounts/${authCode}`)
.set(response);
return res.send("Well done, account integration is completed. You can now close the window and go back to the app");
});
});

The answer selected is not completely correct:
If you dont assign the account_id to a user then it's of no use.
The only way to pass the user_id (fUser.uid) is to pass it using the state parameter.
exports.StripePI = functions.https.onRequest(async (req, res) => {
// console.log('accountIdq ' + req.query.error);
// console.log('accountIdq ' + req.query.state);
// return;
// if(!req.query.code)
// return res.send("An Error has occured please try again");
const response = await stripe.oauth.token({
grant_type: 'authorization_code',
code: req.query.code,
}).then(async response => {
var connected_account_id = response.stripe_user_id;
await admin.firestore().collection('Registration').doc(req.query.state)
.update({customer_id : connected_account_id});
return res.send("Well done, account integration is completed. You can now close the window and go back to the app");
});
});

If you want to create an in-app stripe connect account registration with flutter you will need these:
A server or service to complete the OAuth like Firebase Functions or Integromat (I used Integromat)
A link that will redirect to your app (I used Firebase Dynamic Link)
STEPS TO CREATE THE REGISTRATION FLOW
INTEGROMAT/FIREBASE FUNCTIONS SETUP
I decided to use Integromat instead of Firebase Functions because is easier to set up, doesn't need any code, and decreases my server load.
If you want to create it on Firebase Functions you will need to have a Blaze Plan
If you don't know it, Integromat will automate processes that you currently handle manually, via webhooks. It is not only capable of connecting apps (like GoogleCloud, Facebook, AWS...) but can also transfer and transform data.
Create a new scenario and add a Custom Webhook. Click on it and click on add, name it, and save it. It will now create a custom link to your webhook.
Close and click on the semi-sphere next to the webhook, to add the new module.
Select HTTP and Make a Request.
In the URL section insert https://connect.stripe.com/oauth/token.
Method POST.
Body Type Application/x-www-form-urlencoded.
Create now those fields :
Key client_secret - value your stripe client secret You can find it on your stripe dashboard. I advise you to first use the test mode and after that, change the value to the live key.
Key grant_type - value authorization_code
Key code - leave the value blank. We will add it later.
Save and close
For Firebase Functions you can create a new HTTPS function (I didn't test this)
var stripe = require("stripe")(*your stripe client secret*);
exports.connectStripeStandardAccount = functions.https.onRequest((req, res) =>{
let authCode = req.query.code;
return stripe.oauth.token({
grant_type: 'authorization_code',
code: authCode,
});
});
Remember to install stripe package npm install stripe
STRIPE SETUP
If you are in the test mode go to this link
If you are in the live mode go to this link
Go on the bottom and activate oAuth for standard accounts or for Express Account.
Click on Add URI and add the webhook link of Integromat that you created or the link related to your Firebase function.
If you used Firebase add this link https://us-central1-<project-id>.cloudfunctions.net/connectStripeStandardAccount
For Integromat you will need to create the structure. To do this click on Test OAuth, copy the link, and open it in incognito mode. Open your Integromat scenario and click on your webhook. Now click on Re-determine data structure.
Return to your stripe registration page and click on Ignore account form at the top.
Return on Integromat and select the HTTPS request, modify the field code, and insert the variable code (will open a dialog with all queries from the webhook). Confirm and save.
Now click on the play button and reopen the stripe registration link in incognito mode and click on Ignore account form. Return in Integromat and add a JSON module after the HTTPS request. In the JSON string insert the Data variable and save. Create a Webhook Response module after the JSON module.
In the status put 301, then click on Ok.
DEEP LINK SETUP
It's time to set up the redirect link that will return the user to our flutter app or on our website if the user hasn't it installed.
I used Firebase Dynamic Link You can follow this tutorial for set up.
Go to the dashboard and create a new Link prefix and a new dynamic link, remember to select to redirect your users to the right app.
Click on the three dots in your dynamic link row and click on Link Details. Copy the extended link.
Open Integromat and select the last module you created (Webhook Response). Click on Show advanced settings and on the Header add :
Key Location - value the extended dynamic link that you copied.
If you want your app to elaborate data from the stripe OAuth response you can modify the extended dynamic link by adding ? on the link parameter: link=https://test.page.link?stripe_user_id={{14.stripe_user_id}}
And select the variable parsed from the JSON module. Remember to click on the save icon to save your scenario.
On Firebase Functions you can do this when the function stripe.oauth.token finish (I didn't test it):
res.setHeader('Location', your dynamic link);
res.status(301).send();
Remember to deploy it.
FLUTTER APP SETUP
The code here is very simple. To initialize the connect account registration you only need to set up a button that will launch the stripe connect URL. You can use launch(url);
You can find that URL here. Remember to be logged in to your stripe account to get the right stripe client id. You can easily get it in the same section you added the webhook link in your stripe connect settings.
Delete &redirect_uri=https://sub2.example.com on the URL.
Now you can test your app and will see that when you complete your stripe connect registration/login you will be redirected to your app.
If you want to have an in-app web view you can use this package
To handle the response, you need to have installed the package firebase_dynamic_links
Set your Main widget Stateful and on the initState run the method getDynamic() :
void getDynamic() {
FirebaseDynamicLinks.instance.getInitialLink().then((value) {
if (value != null) {
_connect(value);
}
});
FirebaseDynamicLinks.instance.onLink(onSuccess: (value) async {
if (value != null) {
_connect(value);
}
}, onError: (error) async {
debugPrint('DynamicLinks onError $error');
});
}
void _connect(value) {
Uri deepLink = value.link;
print("Link :" + deepLink.path);
print("Query :" + deepLink.queryParameters.toString());
String stripeUserId = deepLink.queryParameters["stripe_user_id"];
}
You need to have both of them to handle dynamic links when your app is running and when it's closed.

Related

How to interact with html response from http request in Flutter

I have a Flutter app where I am running a Google Apps Script through an http request. The purpose of the script is to create a Form and link the responses to a spreadsheetID that is passed in. The script is configured to only allow Google accounts access it and I've set up the flutter app to use a Service Account to access the script using the format:
getCredentials().then( (AuthClient client){
response = client.get(url, headers{"Authorization": "Bearer ${client.access_token}");
});
Issue: The issue is that the first time that the Service Account makes a request it will get an HTML response saying that it the account needs to give permission to the script to access its data and I'm not sure how to do that.
I'm fairly new to making http requests and using it with the GoogleAPI so I'm stuck. Any advice?
Goal
Create a web page which anyone can use to submit a Google sheet link and for the app to create a form and link the sheet to that.
Authorization
For this users will require a google account and they will be required to go through the OAuth process to authorize your app.
To create the form and link it from client-side JavaScript you would indeed need to call the Apps Script API, though you cannot do this with a service account.
From: https://developers.google.com/apps-script/api/how-tos/execute
Warning: The Apps Script API doesn't work with service accounts.
Luckily, you don't need a service account to do this.
Instructions
Create an Apps Script project with a function something like:
function createForm(ssID){
form = FormApp.create("Your New Form");
form.setDestination(FormApp.DestinationType.SPREADSHEET, ssID);
let formLink = form.getPublishedUrl();
return formLink;
}
Save and take a note of the ID of the script project.
Set up a GCP project (sounds like you already have one).
Make sure the Apps Script API is enabled in your GCP.
Configure the OAuth consent screen and add the scope - https://www.googleapis.com/auth/forms.
Create an API key and a Client ID - add http://localhost:8000 or whatever port you are testing on to the "Authorized JavaScript Origins"
Create OAuth credentials "web browser (JavaScript)".
Link your Apps Script project to the same GCP project - Instructions
Deploy the Apps Script project as an API executable - take not of the deployment ID, although the documentation says that you need the script ID, it is wrong, at least with the new Apps Script IDE.
Write the client-side JavaScript in your app like what is found in the quickstart. Which will enable users to authorize the app. You need to add in the scopes and keys there too. I recommend just following the quick start steps first to get a feel for it. You can use the authorization parts without modification.
Then add in the function that will call your Apps Script, something like this:
function appsScriptCreateForm(ssId) {
var scriptId = "<DEPLOYMENT_ID>";
// Run your Apps Script function
gapi.client.script.scripts
.run({
scriptId: scriptId,
resource: {
function: "createForm",
parameters: [ssId],
},
})
.then(function (resp) {
var result = resp.result;
// ERROR HANDLING
if (result.error && result.error.status) {
appendPre("Error calling API:");
appendPre(JSON.stringify(result, null, 2));
} else if (result.error) {
var error = result.error.details[0];
appendPre("Script error message: " + error.errorMessage);
if (error.scriptStackTraceElements) {
appendPre("Script error stacktrace:");
for (var i = 0; i < error.scriptStackTraceElements.length; i++) {
var trace = error.scriptStackTraceElements[i];
appendPre("\t" + trace.function + ":" + trace.lineNumber);
}
}
// IF SUCCESSFUL
} else {
console.log("success", resp);
}
});
}
Write your HTML with the buttons and inputs necessary.
Add event listeners where appropriate.
Profit!
Please note
This set up is your project running with the authorization of other accounts.
The API requests count against your quota.
You can see details of all the executions in your GCP Project Dashboard.
Users require a Google account and need to authorize the app.
In the Apps Script function above, you just need to pass in the Spreadsheet ID. Not the whole link. You could ask for the whole link and then use Regex to extract the ID if you wanted.
This can be quite tricky and easy to miss a step or make a mistake, so double check your work.
If, after successful authorization, when trying to run the script you get a 404 error, the request has been built wrong, check your IDs. If you get a 500 error, that can mean that the Apps Script function has successfully been called, but, there was an error within Apps Script and failed, check the executions page of the Apps Script editor.
References
Apps Script How to Execute
Apps Script JS Quickstart - Highly recommended you follow these steps first and get that working!
How to link your Apps Script to GCP

Is there a way to get a users Bitmoji using Access tokens from Snapkit login web api?

I am attempting to use the snapkit login web api for a hybrid application. I have successfully been able to intercept the access token in the redirectURL. I was wondering if there was a way to get the users Bitmoji using this access_token and either the functions found in login.js or an http get call?
Api docs: https://docs.snapchat.com/docs/login-kit/#web
currently I have the access_token in a deeplinking function on my app.component.ts . I have attempted to push to a new page with the navController and passing in the access_token as a parameter, but this doesn't help when attempting to get the users information.
Thanks in advance for your help.
Here is the Deeplinking where I intercept the access_token using myapp://settings-set/ as the URL redirect and attempt to push a new page with the matching url.
platform.ready().then(() => {
// Okay, so the platform is ready and our plugins are available.
// Here you can do any higher level native things you might need.
statusBar.styleDefault();
splashScreen.hide();
this.deeplinks.routeWithNavController(this.nav,{
'/settings-set/:token': SettingsSetPage
}).subscribe((match) => {
// match.$route - the route we matched, which is the matched entry from the arguments to route()
// match.$args - the args passed in the link
// match.$link - the full link data
this.nav.push(SettingsSetPage, {
args: match
});
console.log('Successfully matched route', match.$args);
},
(nomatch) => {
// nomatch.$link - the full link data
console.error('Got a deeplink that didn\'t match', nomatch);
});
});
}
In the setting-set page I recieve the parameter using:
this.args = navParams.get('args');
console.log("this is args", JSON.stringify(this.args));
but don't know how to use the information to get the users information
The Bitmoji API can be very confusing at times. I suggest using Passport, a Node JS tool for OAuth, along with the Ionic framework. Snapchat has a guide that explains how to grab specific fields, such as user name and Bitmoji avatar, from a user's Snapchat profile using passport. You can follow this tutorial to learn how to integrate Node JS into your existing ionic app.
So in conclusion, try following these steps:
Integrate Node JS into your existing ionic app
Install Passport and follow Snapchat's guide for obtaining specific fields from the user's profile
Yes, like Mora said you can use passport which will make your life easier. We also have a sample passport app running here:
From the context you provided it seems like you have generated the code and not the access_token. After you get the code from the redirect url, you need to use the code to generate the access token. Check section 2.5 here.
Once you have the access token you can use that to request information. The crux of this lies in setting the "scope" correctly. To get the Bitmoji avatar make sure you set your scope to this at the very least:
var scope = ['https://auth.snapchat.com/oauth2/api/user.bitmoji.avatar'];
Hope this helps!

Retrieving email with Google Sign In for Google Home

so I've beentrying to retrieve the email associated with the current user using the google home.
Documentation is kind of hard to find on the subject and from what I could gather, I should be able to use the SignIn class from actions-on-google. So here is my setup.
DialogFlow -> Created two intent, one to start the sign in process, the other to follow up on the process. (The second one has the event 'actions_intent_SIGN_IN' to it.)
Actions on google config : Account Linking.
Selected - Yes, allow users to sign up for new accounts via voice
Selected - Linking Type : Google Sign In
And added the client id to my fulfillment layer by adding the clientId to my dialogflow config.
dialogflow({clientId})
So, when I run this in the emulator (I get the same exact thing on my google home device) I get an error as soon as my sign in goes to the followup intent (actions_intent_SIGN_IN), which is that my signin.status is Error. From there, I don't know what I can do to get more information on what this error is and how to fix it.
Any idea ? Thanks !
PS : It might not even be something that can be done ? Is there any other way to retrieve the email of the user ? I was able to retrieve it's name using Permission, but there's nothing more that SignIn for email as far as I know.
I can show you how I get my email address with the Google Account linking:
You need to have your accessToken available then you could use what is shown in this answer. The accessToken is in conv.user.access.token when the SignIn is completed.
In node this looks like that:
let link = "https://www.googleapis.com/oauth2/v1/userinfo?access_token="+accessToken;
return new Promise(resolve => {
request(link,(error, response, body) => {
if (!error && response.statusCode === 200) {
let data = JSON.parse(body);
let name = data.given_name ? data.given_name : '';
conv.ask(new SimpleResponse({
speech: "Hello "+ name + "!",
text: "Hello "+ name + "!"
}));
resolve();
} else {
console.log("Error in request promise: "+error);
resolve();
}
})
})
Everything you need should be in the data object.
Hope it helps.
About your error, I am not sure but try doing all step in the link above. I'm using the Sign-In required box with Dialogflow and not a new SignIn but it should work the same.

Actions on Google implicit account linking works in simulator/browser, but not on device (via Google Home app)

I've implemented the implicit flow for Actions on Google account linking, and am using Dialogflow (previously API.AI) to define intents.
The full flow works in the device simulator (from AOG). The first intent gets a "It looks like your account isn't linked yet..." response, and the debug pane includes a URL to initiate linking:
https://assistant.google.com/services/auth/handoffs/auth/start?account_name=[account]#gmail.com&provider=[project_id]_dev&scopes=email&return_url=https://www.google.com/
If I follow this URI in a cache-less window:
I'm redirected to my app's authentication page
I choose to sign in with my Google account (same as [account] above)
I'm redirected to google.com with a success message in the URI bar
The simulator now accepts actions via my app and responds correctly
However, if I follow the same flow using a physical Google Home & the gH app for Android.
Device tells me account not yet linked
Open Google home and follow 'Link to [my app]' link
Browser opens to authentication page
Sign in as user
Redirected to a white page with a single link "Return to app", which has an href: about:invalid#zClosurez
Linking was unsuccessful, so additional attempts to run intents on the Google Home get the same "Account not yet linked" response.
I've inspected the intermediate access_token and state variables at length, and they all match and look to be correctly formatted:
Authentication URL (app sign in page): https://flowdash.co/auth/google?response_type=token&client_id=[client_id]&redirect_uri=https://oauth-redirect.googleusercontent.com/r/[project_id]&scope=email&state=[state]
After authenticating, redirected to (this is the white screen with 'return to app' broken link): https://oauth-redirect.googleusercontent.com/r/genzai-app#access_token=[token]&token_type=bearer&state=[state]
So, it seems there's something non-parallel about the way the simulator and physical devices work in terms of implicit flow account linking.
I've been struggling with this, and with the AOG support team for a very long time to no avail. Anyone else see a similar issue?
Updated with response redirect code:
Login handled by react-google-login component with profile & email scopes. On success we call:
finish_auth(id_token) {
let provider = {
uri: '/api/auth/google_auth',
params: ['client_id', 'redirect_uri', 'state', 'response_type'],
name: "Google Assistant"
}
if (provider) {
let data = {};
provider.params.forEach((p) => {
data[p] = this.props.location.query[p];
});
if (id_token) data.id_token = id_token;
api.post(provider.uri, data, (res) => {
if (res.redirect) window.location = res.redirect;
else if (res.error) toastr.error(res.error);
});
} else {
toastr.error("Provider not found");
}
}
provider.uri hits this API endpoint:
def google_auth(self):
client_id = self.request.get('client_id')
redirect_uri = self.request.get('redirect_uri')
state = self.request.get('state')
id_token = self.request.get('id_token')
redir_url = user = None
if client_id == DF_CLIENT_ID:
# Part of Google Home / API.AI auth flow
if redirect_uri == "https://oauth-redirect.googleusercontent.com/r/%s" % secrets.GOOGLE_PROJECT_ID:
if not user:
ok, _email, name = self.validate_google_id_token(id_token)
if ok:
user = User.GetByEmail(_email, create_if_missing=True, name=name)
if user:
access_token = user.aes_access_token(client_id=DF_CLIENT_ID)
redir_url = 'https://oauth-redirect.googleusercontent.com/r/%s#' % secrets.GOOGLE_PROJECT_ID
redir_url += urllib.urlencode({
'access_token': access_token,
'token_type': 'bearer',
'state': state
})
self.success = True
else:
self.message = "Malformed"
else:
self.message = "Malformed"
self.set_response({'redirect': redir_url}, debug=True)
I am able to make it work after a long time. We have to enable the webhook first and we can see how to enable the webhook in the dialog flow fulfillment docs If we are going to use Google Assistant, then we have to enable the Google Assistant Integration in the integrations first. Then follow the steps mentioned below for the Account Linking in actions on google:-
Go to google cloud console -> APIsand Services -> Credentials -> OAuth 2.0 client IDs -> Web client -> Note the client ID, client secret from there -> Download JSON - from json note down the project id, auth_uri, token_uri -> Authorised Redirect URIs -> White list our app's URL -> in this URL fixed part is https://oauth-redirect.googleusercontent.com/r/ and append the project id in the URL -> Save the changes
Actions on Google -> Account linking setup 1. Grant type = Authorisation code 2. Client info 1. Fill up client id,client secrtet, auth_uri, token_uri 2. Enter the auth uri as https://www.googleapis.com/auth and token_uri as https://www.googleapis.com/token 3. Save and run 4. It will show an error while running on the google assistant, but dont worry 5. Come back to the account linking section in the assistant settings and enter auth_uri as https://accounts.google.com/o/oauth2/auth and token_uri as https://accounts.google.com/o/oauth2/token 6. Put the scopes as https://www.googleapis.com/auth/userinfo.profile and https://www.googleapis.com/auth/userinfo.email and weare good to go. 7. Save the changes.
In the hosting server(heroku)logs, we can see the access token value and through access token, we can get the details regarding the email address.
Append the access token to this link "https://www.googleapis.com/oauth2/v1/userinfo?access_token=" and we can get the required details in the resulting json page.
`accessToken = req.get("originalRequest").get("data").get("user").get("accessToken")
r = requests.get(link)
print("Email Id= " + r.json()["email"])
print("Name= " + r.json()["name"])`
Not sure which python middleware or modules you are using but
self.set_response({'redirect': redir_url}, debug=True)
seems to be setting parameters for a returning a response which isn't correct. Instead you should redirect your response to the redirect_url. For example importing the redirect module in Flask or Django like:
from flask import redirect or from django.shortcuts import redirect
then redirect like:
return redirect(redirect_url)
It appears Google has made a change that has partially solved this problem in that it is now possible to complete the implicit account linking flow outside of the simulator, in the way outlined in my question.
It seems the problem stemmed from an odd handling (on the AOG side) of the client-side redirect case used after sign in with the Google sign-in button.
From Jeff Craig in this thread:
The current workaround, where we provide the "Return to app" link
currently what we're able to provide. The issue is with the way that
redirecting to custom-scheme URIs is handled in Chrome, specifically,
with regard to the redirect happening in the context of a user action.
XHR will break that context, so what is happening is that you click
the Google Sign-In Button, which triggers an XHR to Google's servers,
and then you (most likely) do a client-side redirect back to the
redirect_url we supply, our handler executes, and isn't able to do a
JS redirect to the custom scheme URI of the app, because were outside
of the context of a direct user click.
This is more of a problem with the Implicit (response_type=token) flow
than with the authorization code (response_type=code) flow, and the
"Return to app" link is the best fallback case we currently have,
though we are always looking for better solutions here as well.
The current behavior shows the 'Return to app' link, but as of last week, this link's href is no longer about:invalid#zClosurez, but instead successfully completes the sign-in and linking process. It's an odd and confusing UX that I hope Google will improve in the future, but it was sufficient to get my app approved by the AOG team without any changes to my flow.

Redirect after user has logged in

I'm pretty new to Angular, and right now I'm just trying to get all my routes set up and working as I'd like.
Setup:
When a user navigates to certain pages (/settings for this example) the app should check if there is a user already logged in. If there is continue as usual. Otherwise the user should go to the login page (/login).
What I'd like:
After the user has successfully logged in they should go to the page they were originally trying to get to (/settings)
My question:
Is there an "Angular way" to remember where the user was trying to go to?
Relevant code:
app.js
.when('/settings', {
templateUrl: '/views/auth/settings.html',
controller: 'SettingsCtrl',
resolve: {
currentUser: function($q, $location, Auth) {
var deferred = $q.defer();
var noUser = function() {
//remember where the user was trying to go
$location.path("/login")
};
Auth.checkLogin(function() {
if (Auth.currentUser()) {
deferred.resolve(Auth.currentUser());
} else {
deferred.reject(noUser());
}
});
return deferred.promise;
}
}
})
login.js
$scope.submit = function() {
if(!$scope.logInForm.$invalid) {
Auth.login($scope.login, $scope.password, $scope.remember_me)
//go to the page the user was trying to get to
}
};
Much thanks to John Lindquist for the video which got me this far.
First off, you do not want to redirect the user to a login page.
An ideal flow in a single page web app is as follows:
A user visits a web site. The web site replies with the static assets for the
angular app at the specific route (e.g. /profile/edit).
The controller (for the given route) makes a call to an API using $http, $route, or other mechanism (e.g. to pre-fill the Edit Profile form with details from the logged in user's account via a GET to /api/v1/users/profile)
If/while the client receives a 401 from the API, show a modal to
login, and replay the API call.
The API call succeeds (in this case, the user can view a pre-filled Edit Profile form for their account.)
How can you do #3? The answer is $http Response Interceptors.
For purposes of global error handling, authentication or any kind of
synchronous or asynchronous preprocessing of received responses, it is
desirable to be able to intercept responses for http requests before
they are handed over to the application code that initiated these
requests. The response interceptors leverage the promise apis to
fulfil this need for both synchronous and asynchronous preprocessing.
http://docs.angularjs.org/api/ng.$http
Now that we know what the ideal user experience should be, how do we do it?
There is an example here: http://witoldsz.github.com/angular-http-auth/
The example is based on this article:
http://www.espeo.pl/2012/02/26/authentication-in-angularjs-application
Good luck and happy Angularing!