Issue
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: [email protected], 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
Solution
After a lots 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.
- PCKE (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 PCKE 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 PCKE 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.
Answered By - hansaplast
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.