Authentication flow with Oauth2 in flutter communicating with own api - flutter

After some hours of research in vain I stay confused how to do the following:
I have a flutter app which authenticates via OAuth2 to Google (google_sign_in) and Facebook. For Facebook this is the code:
final LoginResult loginResult = await FacebookAuth.instance.login();
final userData = await FacebookAuth.instance.getUserData();
print(userData);
Which prints: {email: john.doe#email.com, id: 123456, name: John Doe}
I already have a webpage with OAuth2 authentication built in Flask/Python. Now I want my users to be able to both use Web and App and share the preferences/data/etc.
How would I achieve that? In my Flask webapp I'm just creating a user in my database if it doesn't exist and then use some authentication headers in subsequent calls. So I thought with the app I could…
send what I got from OAuth to the api and create the user if it does not yet exist
return some sort of token (with a TTL?)
verify the tokens being sent by the app
But this is a lot of custom boilerplate code, I'm sure that this is existing somewhere/somehow. Additionally: How can I be sure someone is not "tampering" my app via decompile, proxying or just plainly calls my api and claiming to be someone else?
My security requirements are medium: The app will eventually have messaging but won't be used for things like money transfer.
I'm considering these options:
PKCE but this looks like the OAuth2 flow would go through my flask api and that sounds too complex (I had a hard time already getting OAuth2 to work in flutter alone)
Resource Owner Password Credentials Grant which sounds like I can somehow pass the results of OAuth2 to my api, get back a token and use this in subsequent requests. However this seems like an outdated protocol (top google results are articles from oracle)
firebase implementation: they use the same flow: first OAuth2 authentication and then passing the credentials into their servers api. On the first time they pass the credentials a user is created and stored in the database, etc. But my reverse engineering skills are not good enough to figure out how it's done.
using a webview and use the oauth2 of my flask website. I'm shying back from this because it would be not a nice mobile experience plus I would not know how to read/store these credentials

After a lot of reading I found a good article on auth0 , in essence there are two options:
Resource Owner Password Flow - use this if you totally trust your app, e.g. when you deploy it to a closed group of users for which you have device management in place. This situation doesn't apply for me and also Auth0 doesn't recommend it. Still, it would have been relatively easy to implement.
PKCE (Proof Key for Code Exchange) - use this when the client cannot be trusted (IMO 99.9% of mobile apps). But this needs some fancy protocol between the mobile app and the server and alone by looking at the flowchart diagram I got headaches
As PKCE looks too complicated to implement myself I decided to go with Firebase, which helps small projects such as mine where you don't want to go through the pain to code the whole PKCE flow yourself.
What I did was:
adding firebase authentication to my flask app, using flask-firebase - this was worth it since it decreased the lines of python code by 40%. Because the module lacks good documentation I wrote this blog post which explains how to use it
adding firebase authentication to flutter. This is very well documented e.g. here
The whole flow then works like this:
flutter triggers the oauth flow for e.g. google
flutter gets back the auth details, including email address, name, etc. (depends on oauth provider)
the auth details are sent to firebase which creates the user if it doesn't exist yet, enriches it with a user id and packs it into an encrypted token
the token is sent to flask, which verifies the token against firebase
flask logs the user in (via flask_login) and returns a session cookie
the session cookie is stored in flutter (using requests) and used for subsequent api calls
to preserve the user logged in even after app close, the session is stored in apps preferences (using shared_preferences)
In essence, this is the code needed (google social login example):
Future<String?> signInWithGoogle() async {
final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
final GoogleSignInAuthentication? googleAuth =
await googleUser?.authentication;
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth?.accessToken,
idToken: googleAuth?.idToken,
);
UserCredential userCredentials =
await FirebaseAuth.instance.signInWithCredential(credential);
return userCredentials.user?.getIdToken();
}
…
var cookies = await Requests.getStoredCookies('example.com');
SharedPreferences? prefs;
if (!cookies.keys.contains('session')) {
prefs = await SharedPreferences.getInstance();
if (prefs.containsKey('session')) {
print('cookie not set, load session from prefs');
await Requests.addCookie(
'example.com', 'session', prefs.getString('session')!);
}
}
cookies = await Requests.getStoredCookies('example.com');
if (!cookies.keys.contains('session')) {
print('cookie not set, prefs contain no session, signing in');
String? idToken = await signInWithGoogle();
if (idToken != null) {
await Requests.post('https://example.com/auth/sign-in',
body: idToken,
headers: {'Content-Type': 'application/jwt'},
bodyEncoding: RequestBodyEncoding.PlainText);
var cookies = await Requests.getStoredCookies('example.com');
prefs?.setString('session', cookies['session']!.value);
}
}
var r = await Requests.get('https://example.com/api/something_which_requires_login');
The important part happens with Requests.post: this posts the idToken of firebase to flask, which in turn then verifies the token, calls login_user and returns response with the session cookie header. This cookie is stored by requests and is added to subsequent http requests.
Because this is some mouthful I created this blogpost which explains this in more detail.

Related

I want Microsoft Authentication in Flutter Application

I want to provide Microsoft auth in my flutter app along with Google and Facebook. I found documentation for Google and Facebook, but could not find any resource or document for Microsoft auth. Any help will be appreciable.
Sorry if this answer is late, it may help others who are facing the same issue.
I followed a simple technique to overcome Microsoft authentication, at the end you will receive the user mail ID, and other details as per permissions set while declaring the app.
There is a flutter package https://pub.dev/packages/flutter_web_auth
Before proceeding you need to register an application with the Azure Active Directory https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/CreateApplicationBlade/isMSAApp~/false
configure the flutter login method
final url = Uri.https('login.microsoftonline.com', '/your-tenant ID/oauth2/v2.0/authorize', {
'response_type': 'token',
'client_id': 'your Client ID',
'redirect_uri': redirectUrl,
'scope': 'https://graph.microsoft.com/openid',
});
// This method will return the authorization code which needs to exchanged for user details
final result = await FlutterWebAuth.authenticate(url: url.toString(), callbackUrlScheme: redirectUrl);
// Extract code from resulting url
you can get either access code or token based on security level. More explanation can be found at: https://learn.microsoft.com/en-us/azure/active-directory/develop/app-sign-in-flow
for my approach, I took tokens from the Azure AD and exchange with this API to get user details
final details = await http.get(Uri.parse("https://graph.microsoft.com/oidc/userinfo"),
headers: {
"Authorization" : "Bearer "+accessCode,
});
You can use these details to authenticate the user and enable functions.
In Azure AD you can define who can use the login mechanism such as organization or general.
Hope this helps.

Running Google Apps Script through https request with Service Account credentials

I'm working on a Flutter app. And I've been trying to run my web-app Google Apps Script through http request since I'm required to use a Service Account and that access isn't supported in the Apps Script API. But I keep getting a 403/Forbidden response to the requests. I have the credentials for the Service Account and I am using its access token in my request but it still doesn't work.
I'm a novice at http requests and new to Google's authentication protocols so I'd appreciate some insight.
Thanks in advance.
Code:
return await driveUtils.getCreds(context).then((creds) async {
final drive_scopes = [drive.DriveApi.DriveReadonlyScope, "https://www.googleapis.com/auth/drive.file"];
final script_scopes = [app_scripts.ScriptApi.ScriptDeploymentsScope];
return await clientViaServiceAccount(creds, script_scopes+drive_scopes).then((AuthClient client) async {
debugPrint("url = " + url);
debugPrint("token = " + client.credentials.accessToken.data);
return await client.get(url,
headers: {
"Authorization": "Bearer ${client.credentials.accessToken.data}"
}
);
}, onError: onClientError);
}, onError: onCredsError);
Background: The script creates a Form and sets it Destination to a Spreadsheet's ID. Hence, the app requires that anyone who runs it to have a Google account to become the owner of the new Form and obtain access to the Sheet.
Update: It seems that Service Accounts can only access scripts that are within the same Google Cloud Project. This is a big issue since the point of the script is to create a central place for acquiring Form creation functionality for my app. And the app is intended to be used by anyone.
Does anyone have any ideas? Assuming a Service Account is the right Google Credentials for my app, I essentially need the ability to:
Create a Form that can be assigned to a user
Designate a user's spreadsheet as the forms response location
Retrieve the forms publishedURL
#Tanaike helped me figure out the issue. In order to make the script visible and able to run with a Service Account I had to change the Share setting for viewing the script. Simple solution

Passing user id with AuthController

I just made simple authentication app using aqueduct as a back end. I used codes from aqueduct documentation pages for login and registering. When I login with this code in backend
router
.route('/auth/token')
.link(() => AuthController(authServer));
I get back token, token type and expiration date, Is there any chance to also pass userId? Or do I have to create my own controller to do that?
UPDATE
or how can I in my backend to save user id when saving the data
#Operation.post()
Future<Response> addData(#Bind.body(ignore: ['id']) Data newData) async {
final query = Query<Data>(context)..values = newData;
final insertData = await query.insert();
return Response.ok(insertData);
}
Flutter frontend
Login initially with the username/email and password. You will get an authorization token back from the server if the username and password are valid. Then use that token to make further privileged requests to the server.
You don't need to save any personal data about the user (email or password) on the client. You can save the token, though, if you don't want to make the user log in again the next time they use the app. When saving the token you should use a secure storage option. The flutter_secure_storage plugin uses KeyChain on iOS and KeyStore on Android.
Aqueduct backend
You can use the user IDs all you want on the backend. I don't know of any need to pass them to the client, though. On the backend you can query the user ID and then use it to fetch other information from the database.
Here is an example from the documentation:
class NewsFeedController extends ResourceController {
NewsFeedController(this.context);
ManagedContext context;
#Operation.get()
Future<Response> getNewsFeed() async {
var forUserID = request.authorization.ownerID;
var query = Query<Post>(context)
..where((p) => p.author).identifiedBy(forUserID);
return Response.ok(await query.fetch());
}
}
The client only passed in the token. Aqueduct looks up the user id for you based on that token. Now you know the user ID.
Your other tables can have a column for the user ID so that only that user may save and retrieve their data. In the example above, Posts have an Author and an Author has an ID, that is, the user ID.
where((p) => p.author).identifiedBy(forUserID)
is equivalent to
where((p) => p.author.id).equalTo(forUserID)
You can read about this in the Advanced Queries section of the documentation.

ADFS single sign on for logged on user not working

I am trying to integrate our application a regular .net desktop service that runs in the user context with ADFS, and a Web API. The user is a domain user and this is a regular AD environment. The MVC aspect of the website are working well with the browser.
I am on ADAL3(VS2017), windows server 2016, I referred this link [ADFS SSO CloudIdentity] am able to use UserCredentialPassword to get a token successfully and call into my Web API, I just followed the steps and changed things to async.
However using the logged on credential bit where a new UserCredential() should indicate to call to get the token from current thread doesn't work at all for me, on ADAL 2, I am getting null pointer exception and on ADAL3 I get
MSIS7065: There are no registered protocol handlers on path /adfs/oauth2/token to process the incoming request.
I did see a similar query on SO but based on my understanding the OP there is falling back on browser based behaviour.
Falling back at browser based auth (with Promptbehaviour.never) based behaviour is undesired because I have seen a lot of config issues on customer site where even with the relevant settings enabled the browser was assuming it to be an internet site and logging in fails.
Code for reference
string authority = "https://adfs.domain.com/adfs";
string resourceURI = "https://local.ip:44325";
string clientID = "19cda707-b1bf-48a1-b4ac-a7df00e1a689";
AuthenticationContext ac = new AuthenticationContext(authority, false);
// I expect that the user's credential are picked up here.
AuthenticationResult authResult =
await ac.AcquireTokenAsync(resourceURI, clientID,
new UserCredential());
string authHeader = authResult.CreateAuthorizationHeader();
var client = new HttpClient();
var request = new HttpRequestMessage( HttpMethod.Get,
"https://local.ip:44325/api/values");
request.Headers.TryAddWithoutValidation( "Authorization", authHeader);
var response = await client.SendAsync(request);
string responseString = await response.Content.ReadAsStringAsync();
The call fails in acquiretokenasync.
Have you violated any of the constraints described here?
Also, similar question here around ADAL V3.0 and UserCredential support.
In the link above, the RP was created using PowerShell. Are you sure that you have properly configured an application in ADFS 4.0 - clientID etc.

MSAL, Azure MobileService and Auto REST calls get 401 Unauthorized

I have an app (currently in UWP) that makes use of MobileServiceClient and AutoRest to an Azure App Service API App. I successfully used the winfbsdk and can authenticate thru that and then get it to login to MobileService.LoginAsync with the FB access token as a JObject. I also take that JObject and send it in the x-zumo-auth header when making calls to the API App via AutoRest within the app.
What I would like to do is be able to authenticate using MicrosoftAccount. If I use MobileService.LoginAsync, I cannot get the proper token and pass it along to AutoRest - it always comes back as 401 Unauthorized.
I tried to used MSAL, but it returns a Bearer token and passing that along also comes back as 401 Unauthorized.
Is there any good way to do this? I started on the route of MSAL since that would support Windows desktop, UWP and Xamarin Forms which will be ideal. I just need info on how to get the proper token from it to pass along to an AutoRest HttpClient that goes back to the Azure App Service API App.
Update:
If I use the following flow, it works with Facebook, but not with MicrosoftAccount.
-Azure AppService with WebAPI (and swagger for testing via a browser)-Security setup through the Azure Dashboard on the service and configured to allow Facebook or MicrosoftAccount
1. On my UWP app, using winfbsdk, I login with Facebook, then grab the FBSession.AccessTokenData.AccessToken and insert that into a JObject:
JObject token = JObject.FromObject
(new{access_token = fbSession.AccessTokenData.AccessToken});
2. Login to MobileServiceClient
user = await App.MobileService.LoginAsync
(MobileServiceAuthenticationProvider.Facebook, token);
Login to API App with HttpClient and retrieve the token to use in X-ZUMO-AUTH
using (var client = new HttpClient())
{
client.BaseAddress = App.MobileService.MobileAppUri;
var jsonToPost = token;
var contentToPost = new StringContent(
JsonConvert.SerializeObject(jsonToPost),
Encoding.UTF8, "application/json");
var asyncResult = await client.PostAsync(
"/.auth/login/" + provider.ToString(),
contentToPost);
if (asyncResult.Content == null)
{
throw new InvalidOperationException("Result from call was null.");
return false;
}
else
{
if (asyncResult.StatusCode == System.Net.HttpStatusCode.OK)
{
var resultContentAsString = asyncResult.Content.AsString();
var converter = new ExpandoObjectConverter();
dynamic responseContentAsObject = JsonConvert.DeserializeObject<ExpandoObject>(
resultContentAsString, converter);
var applicationToken = responseContentAsObject.authenticationToken;
ApiAppClient.UpdateXZUMOAUTHToken(applicationToken);
}
}
}
ApiAppClient.UpdateXZUMOAUTH call just does the following:
if (this.HttpClient.DefaultRequestHeaders.Contains("x-zumo-auth") == true)
{
this.HttpClient.DefaultRequestHeaders.Remove("x-zumo-auth");
}
this.HttpClient.DefaultRequestHeaders.Add("x-zumo-auth", applicationToken);
Any subsequent calls using the ApiAppClient (created with AutoRest from the swagger json of my Azure AppService WebAPI) contain the x-zumo-auth header and are properly authenticated.
The problem occurs when trying to use MicrosoftAccount. I cannot seem to obtain the proper token to use in x-zumo-auth from either MSAL or LoginWithMicrosoftAsync.
For #1 above, when trying for MicrosoftAccount, I used MSAL as follows:
AuthenticationResult result = await MSAuthentication_AcquireToken();
JObject token = JObject.FromObject(new{access_token = result.Token});
And MSAuthentication_AcquireToken is defined below, using interfaces and classes as suggested in the Azure samples: https://github.com/Azure-Samples/active-directory-xamarin-native-v2
private async Task<AuthenticationResult> MSAuthentication_AcquireToken()
{
IMSAcquireToken at = new MSAcquireToken();
try
{
AuthenticationResult res;
res = await at.AcquireTokenAsync(App.MsalPublicClient, App.Scopes);
return res;
}
}
Update - ok with MobileServiceClient, but still not working with MSAL
I got it working with MobileServiceClient as follows:
1. Use MobileService.LoginAsync
2. Take the returned User.MobileServiceAuthenticationToken
3. Set the X-ZUMO-AUTH header to contain the User.MobileServiceAuthenticationToken
user = await App.MobileService.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount);
applicationToken = user.MobileServiceAuthenticationToken;
ApiAppClient.UpdateAppAuthenticationToken(applicationToken);
MSAL still not working!
So the original question still remains, what part of the token returned from MSAL do we need to pass on to X-ZUMO-AUTH or some other header so that calls to the Azure AppService WebAPI app will authenticate?
I have an app (currently in UWP) that makes use of MobileServiceClient and AutoRest to an Azure App Service API App. I successfully used the winfbsdk and can authenticate thru that and then get it to login to MobileService.LoginAsync with the FB access token as a JObject. I also take that JObject and send it in the x-zumo-auth header when making calls to the API App via AutoRest within the app.
According to your description, I assumed that you are using Client-managed authentication. You directly contact the identity provider and then provide the token during the login with your mobile back-end, then you could leverage MobileServiceClient.InvokeApiAsync to call your API APP, which would add the X-ZUMO-AUTH header with the value authenticationToken after you invoke MobileServiceClient.LoginAsync(MobileServiceAuthenticationProvider.Facebook, token);
What I would like to do is be able to authenticate using MicrosoftAccount. If I use MobileService.LoginAsync, I cannot get the proper token and pass it along to AutoRest - it always comes back as 401 Unauthorized. I tried to used MSAL, but it returns a Bearer token and passing that along also comes back as 401 Unauthorized. Is there any good way to do this?
AFAIK, for the client-flow authentication patterns (AAD, Facebook, Google), the token parameter for LoginAsync would look like {"access_token":"{the_access_token}"}.
For the client-flow authentication (Microsoft Account), you could leverage MobileServiceClient.LoginWithMicrosoftAccountAsync("{Live-SDK-session-authentication-token}"), also you could use LoginAsync with the token parameter of the value {"access_token":"{the_access_token}"} or {"authenticationToken":"{Live-SDK-session-authentication-token}"}. I have tested LoginAsync with the access_token from MSA and retrieve the logged info as follows:
In summary, when you retrieve the authentionToken after you have logged with your mobile back-end, you could add the X-ZUMO-AUTH header to each of your API APP requests with the authentionToken.
For more details, you could refer to this official document about authentication works in App Service.
UPDATE
I have checked this https://github.com/Azure-Samples/active-directory-xamarin-native-v2 and used fiddler to capture the network packages when authenticating the user and get an access token. I found that MSAL is working against Microsoft Graph and REST and when the user is logged, you could only retrieve the access_token and id_token, and both of them could not be used for single sign-on with your mobile back-end.
While the official code sample about Client-managed authentication for Azure Mobile Apps with MSA is using the Live SDK. As the Live SDK REST API mentioned about signing users, you could get an access token and an authentication token which is used for single sign-on scenario. Also, I have checked the Server-managed authentication and found that app service authentication / authorization for MSA also uses the Live SDK REST API.
In summary, you could not use MSAL for client-managed authentication with MSA, for client-managed authentication, you need to leverage Live SDK to retrieve the authentication_token then invoke MobileServiceClient.LoginWithMicrosoftAccountAsync("{Live-SDK-session-authentication-token}") to retrieve the authenticationToken from your mobile backend. Or you could just leverage server-managed authentication for MSA. For more details about Live SDK, you could refer to LiveSDK.