How to integrate OAuth2 with back- and frontend - django

I'm learning OAuth2 to integrate a 3rd party provider like Facebook or Github. But I'm struggling with finding resources that describe how to integrate it when you're building an app with both a back- and frontend.
In this example, let's pretend a have frontend (myfrontend.com) running something like Vue.js. And a backend (mybackend.com) running something like Django. And a 3rd party provider like GitHub.
My idea is to use this flow:
Front end redirect's to providers Authorize url. example: https://github.com/login/oauth/authorize?client_id=<client_id>&redirect_uri=https://mybackend.com/callback?return-to=https://myfrontend.com/home.
If the user authorizes, backend receives code in /callback.
Backend sends authentication request (serverside) with code to get access_token to 3rd party provider.
Backend creates a user if it doesn't exist yet and saves access_token associated with that user.
Backend creates a new local token for the user (for authenticating to mybackend.com) (JWT or OAuth2).
Backend redirects to https://myfrontend.com/home (specified in first url) with token in body.
Frontend receives token and saves in in local storage.
When the frontend requires a resource from GitHub a behalf of the user, a request is sent to my backend which in turn uses the save access_token and requests the resource from GitHub.
My question is this:
Is this flow safe or should I reconsider?
in step 6, do I have to make sure that redirect-url is to my domain myfrontend.com. Feels like it would leave a security flaw if you check this in a bad way.

Related

django-rest-framework-social-auth requires a client secret for convert token

I am making an Android application with a Django based backend.
The Django server has a REST API to serve user data which I want to protect. I only want my users to log in via Google (or FB which I'm going to implement in the future).
I am using django-rest-framework-social-auth to implement the authorization in the backend.
So the flow as I understand is like this:
The client (my android application), does the whole Google OAuth2.0
flow and obtains the access token
The the client then wants to
convert the Google access token with one with which it can access my
backend server API endpoints. To do this it calls the convert-token
API endpoint implemented in django-rest-framework-social-auth
Now that it has the API access token, it can include that in the header of all further API calls and receive the private data.
Now my concern is, the django-rest-framework-social-auth convert-token endpoint also needs the client-id and client-secret generated by django-oauth for the application. So I will have to store the client-secret somewhere in my mobile app, which entirely defeats the purpose of authenticating whether the request came from the app or not, because someone can decompile the apk and get the app and make token exchange requests on my app's behalf.
Is there any way around this? Is there a "standard" way to implement this which I don't know?

How to send requests to Cloud Run only from the app?

Subscription based iOS app I'm building uses Cloud Run service invoked via HTTPS request.
How can I make sure that the request can only be invoked by app owners(from the app)?
I've looked at Google Sign-In authentication, but I don't think it is applicable in my case as only those subscribed to the app should have the access, not just those with Gmail account.
I think without a Google Sign-in involved, your question has nothing to do with Cloud Run and can be generalized as:
How to send requests to to a backend app only from its mobile app?
So I'll answer that.
You'll find out that you need some form of "authentication" to prove that you're on a mobile app as a "user". To achieve that, you need some form of sign-in.
You may try to ship a secret (like a token or private key) in the application and use that to authenticate, but this will be susceptible to:
exfiltration of the private they from the application bundle through reverse engineering
applying a man-in-the-middle attack to the HTTPS request (and therefore the token) by trusting a root CA on the device and using e.g. mitmproxy to decrypt the request as plaintext.
In reality, there's no way to fully secure iOS/Android <=> backend communication that. Even the largest apps like Twitter, Instagram etc have their APIs reverse engineered all the time and invoked from non iOS/Android clients as the requests can be spoofed.
If you want to authenticate your existing users, you should figure out how these people login to your app. This could be simple username:password in Authentication: Basic [...] header, or something more complicated like OAuth2 which is what apps like Facebook, Twitter implement under the covers for their mobile apps.
Then you would validate that Authentication header in your Cloud Run application code yourself.
So again, I don't think this is a problem specific to Cloud Run, or any cloud provider.
If your goal is for your API to only be called when your users are authenticated in your app, I would recommend implementing one of the two solutions described on this page:
Using Google Sign-in or Firebase Authentication

Django API fronted by Azure API gateway

I have a Django application that currently stores user credentials and performs authorization and authentication. I am in the process of breaking off the front-end into an Angular SPA and converting the backend to a REST API. My Django API will live as an Azure API app protected by Azure API Gateway. I would like to remove the authentication piece from Django and allow users to sign in using OpenID Connect through either Google or Microsoft Account. What I would like to happen is this:
When a user visits the site, assuming they have never registered with my app, they will have the option to sign in with their Google account or Microsoft Account. If the user decides to sign in using their Google or Microsoft account, (this is where I'm confused and why i'm posting here ... ) I think what happens is the API Gateway performs the authentication, generates a JSON Web Token (JWT), and sends that token back to the Django API. Django receives the JWT, decrypts it, and checks to see if there is a user account matching the email address in the JWT. If there is not a user account, Django will add a user to the user accounts table (not storing a password). If there is a user matching that email address, then Django allows the user in.
All that said, I guess my question(s) are:
Should I do the authentication at the API Management Gateway or should I do it at the Azure Web API?
Can I use Django's built-in authentication system to do what I want or is that not needed?
Am I over-complicating all of this? Is there an easier way to do this? All this seems like a lot of work.
Is OpenID Connect what I should be using (instead of Oauth2)? I have no experience with either.
Azure API Management does not actually provide any kind of JWT issuing mechanism, so you'll have to implement that yourself. The end points for doing that may or may not be exposed via API management.
What possibly gets you confused is the fact that the APIm Portal supports various indentity providers, like Twitter or Google, to sign up for the API. But these are not your application users, these are for the API Portal Users.
What you can do with the APIm Gateway is to validate subsequent calls to your backend API that the supplied JWT token is valid (using the <validate-jwt> policy).

Generate an OAuth2 token in a view

Let's say I have an AngularJS application that consumes the REST API of a Django application.
The Django application has got a built-in OAuth2 provider that can be called to retrieve an access token and use the protected endpoints of the API. This provider is using django-oauth-toolkit.
Let's assume there is a registered client with "password" grant type, so that the end users only need to provide their credentials in the front-end in order to get an access token from the back-end.
At some point we want to add some support for social networks login and we decide to use python-social-auth (PSA) to that end. Here is the workflow I want to achieve:
The user logs in on Facebook from the front-end (via the Facebook SDK) and we get an access token back from the OAuth2 provider of Facebook.
We send the Facebook token to an endpoint of our REST API. This endpoint uses the Facebook token and django-social-auth to authenticate the user in our Django application (basically matching a Facebook account to a standard account within the app).
If the authentication succeeds, the API endpoint requests an access token from the OAuth2 provider for this newly authenticated user.
The Django access token is sent back to the front-end and can be used to access the REST API in exactly the same way that a regular user (i.e. logged in with his credentials) would do.
Now my problem is: how do I achieve step 3? I first thought I would register a separate OAuth2 client with Client Credentials Grant but then the generated token is not user-specific so it does not make sense. Another option is to use the TokenAuthentication from DRF but that would add too much complexity to my project. I already have an OAuth server and I don't want to set up a second token provider to circumvent my problem, unless this is the only solution.
I think my understanding of PSA and django-oauth-toolkit is not deep enough to find the best way of reaching my goal, but there must be a way. Help!
I managed to get something working using urllib2. I can't speak towards whether or not this is good practice, but I can successfully generate an OAuth2 token within a view.
Normally when I'd generate an access token with cURL, it'd look like this:
curl -X POST -d "grant_type=password&username=<user_name>&password=<password>" -u"<client_id>:<client_secret>" http://localhost:8000/o/token/
So we're tasked with making urllib2 accomplish this. After playing around for some bit, it is fairly straightforward.
import urllib, urlib2, base64, json
# Housekeeping
token_url = 'http://localhost:8000/auth/token/'
data = urllib.urlencode({'grant_type':'password', 'username':<username>, 'password':<password>})
authentication = base64.b64encode('%s:%s' % (<client_id>, <client_secret>))
# Down to Business
request = urllib2.Request(token_url, data)
request.add_header("Authorization", "Basic %s" % authentication)
access_credentials = urllib2.urlopen(request)
json_credentials = json.load(access_credentials)
I reiterate, I do not know if this is in bad practice and I have not looked into whether or not this causes any issues with Django. AFAIK this will do this trick (as it did for me).

Can I authenticate with OAuth in a Javascript app without saving a token on the client side with rauth?

I want to be able to authenticate users of an angular.js application using oauth, but I do not want to store any tokens on the frontend because I have seen that it can be fairly complicated to do so securely. Is there a way to pass some sort of credentials of a user to my django web application, where is can authenticate the user with some oauth provider and save that information in a session? To make it simple, here is the process I want
User is logged into some oauth provider, i.e. stackexchange
They click a "login with stackexchange" button on the front end angular app
Their login credentials are sent over to the django application through a restful api
The django app which receives these credentials attempts to get a token using rauth
If the server receives a token, the user is logged in and their information is saved in a session, otherwise they are given an error
Is this sort of process supported by OAuth2 providers?
Step 3 is incorrect: that authentication process is handled entirely off-site, on the OAuth provider's infrastructure, e.g. StackExchange.
This kind of flow is certainly possible. I would check out the Facebook example, which uses Flask, but provides a similar framework for how you might go about this in Django.