I was trying set up google authentication with react frontend and django rest framework backend. I set up both the frontend and backend using this two part tutorial, PART1 & PART2. When I try to login with google in the frontend I get POST http://127.0.0.1:8000/google-login/ 400 (Bad Request) I think it's because my google api needs an access token and an authorization code to be passed. After debugging the react js, I noticed the response I get from google doesn't have an authorization code. I suspect because responseType is permission(by default), Source:React login props , instead of code. I was wondering how would you change the response type in react? (I'm not even sure if this alone is the issue)
Here's my backend code
In my views.py file
class GoogleLogin(SocialLoginView):
adapter_class = GoogleOAuth2Adapter
callback_url = "http://localhost:3000"
client_class = OAuth2Client
in my urls.py
path('google-login/', GoogleLogin.as_view(), name='google-login'),
for my front end
/Components/login.js
const googleLogin = async (accesstoken,code) => {
console.log(accesstoken)
let res = await cacaDB.post(
`google-login/`,
{
access_token: accesstoken,
code: code
}
);
console.log(res);
return await res.status;
};
const responseGoogle = (response) => {
console.log(response.code);
googleLogin(response.accessToken, response.code);
}
return(
<div className="App">
<h1>LOGIN WITH GOOGLE</h1>
<GoogleLogin
clientId="client_id"
buttonText="LOGIN WITH GOOGLE"
onSuccess={responseGoogle}
onFailure={responseGoogle}
/>
</div>
)
I want to save the user in the database and have them stay logged in, in the front end.
This Post explains the login flow behind the scene. Here's Login flow image I'm basically stuck on returning code and accesstoken(I can return this successfully) step.
Here's my list of questions,
How do I return code from google?
I have knox token set up, can I
use it instead of the JWT tokens?
Does the class GoogleLogin(SocialLoginView), take care of the steps of validating the access token and code with google and creating the user with that email in database?
Would really appreciate your inputs.
After investigating a bit on my end, I think I might have a solution that works for you.
I've messed with OAuth before, and it's quite tricky sometimes because it has to be robust. So a bunch of security policies usually get in the way.
I'll provide my full step-by-step, since I was able to get it working, trying my best to match what you posted.
Firstly, to have a clean slate, I went off the example code linked in the tutorials. I cloned and built the project, and did the following:
Creating a new project on GCP
Configured the OAuth consent screen
I set the User type to "internal". This options may not be available if you're not using an account under GSuite (which I am). "External" should be fine though, just that "internal" is the easiest to test.
Created a OAuth 2.0 Client
Added http://localhost:3000 to the "Authorized JavaScript origins" and "Authorized redirect URIs" sections
Register a Django superuser
Registered a Site, with value of localhost:8000 for both fields.
Went into the admin panel, and added a Social Application with Client ID and Secret Key as the "Client ID" and "Client Secret" from GCP, respectively. I also picked the localhost site that we added earlier and added it to the right hand box. (I left Key blank)
Example of my Application Page
Filled in the clientId field in App.js, in the params of the GoogleLogin component.
Here's where I ran into a bit of trouble, but this is good news as I was able to reproduce your error! Looking at the request in the network inspector, I see that for me, no body was passed, which is clearly the direct cause of the error. But looking at App#responseGoogle(response), it clearly should pass a token of some sort, because we see the line googleLogin(response.accessToken).
So what is happening is that accounts.google.com is NOT returning a proper response, so something is happening on their end, and we get an invalid response, but we fail silently because javascript is javascript.
After examining the response that Google gave back, I found this related SO post that allowed me to fix the issue, and interestingly, the solution to it was quite simple: Clear your cache. I'll be honest, I'm not exactly sure why this works, but I suspect it has something to do with the fact that development is on your local machine (localhost/127.0.0.1 difference, perhaps?).
You can also try to access your site via incognito mode, or another browser, which also worked for me.
I have knox token set up, can I use it instead of the JWT tokens?
I don't think I have enough knowledge to properly answer this, but my preliminary research suggests no. AFAIK, you should just store the token that Google gives you, as the token itself is what you'll use to authenticate. It seems that Knox replaces Django's TokenAuthentication, which means that Knox is in charge of generating the token. If you're offloading the login work to Google, I don't see how you could leverage something like Knox. However, I could be very wrong.
Does the class GoogleLogin(SocialLoginView), take care of the steps of validating the access token and code with google and creating the user with that email in database?
I believe so. After successfully authenticating with Google (and it calls the backend endpoint correctly), it seems to create a "Social Account" model. An example of what it created for me is below. It retrieved all this information (like my name) from Google.
Example of my "Social Accounts" page
As for how to retrieve the login from the browser's local storage, I have no idea. I see no evidence of a cookie, so it must be storing it somewhere else, or you might have to set that up yourself (with React Providers, Services, or even Redux.
On the website https://code.google.com/apis/console I have registered my application, set up generated Client ID: and Client Secret to my app and tried to log in with Google.
Unfortunately, I got the error message:
Error: redirect_uri_mismatch
The redirect URI in the request: http://127.0.0.1:3000/auth/google_oauth2/callback did not match a registered redirect URI
scope=https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email
response_type=code
redirect_uri=http://127.0.0.1:3000/auth/google_oauth2/callback
access_type=offline
approval_prompt=force
client_id=generated_id
What does mean this message, and how can I fix it?
I use the gem omniauth-google-oauth2.
The redirect URI (where the response is returned to) has to be registered in the APIs console, and the error is indicating that you haven't done that, or haven't done it correctly.
Go to the console for your project and look under API Access. You should see your client ID & client secret there, along with a list of redirect URIs. If the URI you want isn't listed, click edit settings and add the URI to the list.
EDIT: (From a highly rated comment below) Note that updating the google api console and that change being present can take some time. Generally only a few minutes but sometimes it seems longer.
In my case it was www and non-www URL. Actual site had www URL and the Authorized Redirect URIs in Google Developer Console had non-www URL. Hence, there was mismatch in redirect URI. I solved it by updating Authorized Redirect URIs in Google Developer Console to www URL.
Other common URI mismatch are:
Using http:// in Authorized Redirect URIs and https:// as actual URL, or vice-versa
Using trailing slash (http://example.com/) in Authorized Redirect URIs and not using trailing slash (http://example.com) as actual URL, or vice-versa
Here are the step-by-step screenshots of Google Developer Console so that it would be helpful for those who are getting it difficult to locate the developer console page to update redirect URIs.
Go to https://console.developers.google.com
Select your Project
Click on the menu icon
Click on API Manager menu
Click on Credentials menu. And under OAuth 2.0 Client IDs, you will find your client name. In my case, it is Web Client 1. Click on it and a popup will appear where you can edit Authorized Javascript Origin and Authorized redirect URIs.
Note: The Authorized URI includes all localhost links by default, and any live version needs to include the full path, not just the domain, e.g. https://example.com/path/to/oauth/url
Here is a Google article on creating project and client ID.
If you're using Google+ javascript button, then you have to use postmessage instead of the actual URI. It took me almost the whole day to figure this out since Google's docs do not clearly state it for some reason.
In any flow where you retrieved an authorization code on the client side, such as the GoogleAuth.grantOfflineAccess() API, and now you want to pass the code to your server, redeem it, and store the access and refresh tokens, then you have to use the literal string postmessage instead of the redirect_uri.
For example, building on the snippet in the Ruby doc:
client_secrets = Google::APIClient::ClientSecrets.load('client_secrets.json')
auth_client = client_secrets.to_authorization
auth_client.update!(
:scope => 'profile https://www.googleapis.com/auth/drive.metadata.readonly',
:redirect_uri => 'postmessage' # <---- HERE
)
# Inject user's auth_code here:
auth_client.code = "4/lRCuOXzLMIzqrG4XU9RmWw8k1n3jvUgsI790Hk1s3FI"
tokens = auth_client.fetch_access_token!
# { "access_token"=>..., "expires_in"=>3587, "id_token"=>..., "refresh_token"=>..., "token_type"=>"Bearer"}
The only Google documentation to even mention postmessage is this old Google+ sign-in doc. Here's a screenshot and archive link since G+ is closing and this link will likely go away:
It is absolutely unforgivable that the doc page for Offline Access doesn't mention this. #FacePalm
For my web application i corrected my mistake by writing
instead of : http://localhost:11472/authorize/
type : http://localhost/authorize/
Make sure to check the protocol "http://" or "https://" as google checks protocol as well.
Better to add both URL in the list.
1.you would see an error like this
2.then you should click on request details
after this , you have to copy that url and add this on https://console.cloud.google.com/
go to https://console.cloud.google.com/
click on Menu -> API & Services -> Credentials
you would see a dashboard like this ,click on edit OAuth Client
now in Authorized Javascript Origins and Authorized redirect URLS
add the url that has shown error called redirect_uri_mismatch i.e here it is
http://algorithammer.herokuapp.com , so i have added that in both the places in
Authorized Javascript Origins and Authorized redirect URLS
click on save and wait for 5 min and then try to login again
This seems quite strange and annoying that no "one" solution is there.
for me http://localhost:8000 did not worked out but http://localhost:8000/ worked out.
This answer is same as this Mike's answer, and Jeff's answer, both sets redirect_uri to postmessage on client side. I want to add more about the server side, and also the special circumstance applying to this configuration.
Tech Stack
Backend
Python 3.6
Django 1.11
Django REST Framework 3.9: server as API, not rendering template, not doing much elsewhere.
Django REST Framework JWT 1.11
Django REST Social Auth < 2.1
Frontend
React: 16.8.3, create-react-app version 2.1.5
react-google-login: 5.0.2
The "Code" Flow (Specifically for Google OAuth2)
Summary: React --> request social auth "code" --> request jwt token to acquire "login" status in terms of your own backend server/database.
Frontend (React) uses a "Google sign in button" with responseType="code" to get an authorization code. (it's not token, not access token!)
The google sign in button is from react-google-login mentioned above.
Click on the button will bring up a popup window for user to select account. After user select one and the window closes, you'll get the code from the button's callback function.
Frontend send this to backend server's JWT endpoint.
POST request, with { "provider": "google-oauth2", "code": "your retrieved code here", "redirect_uri": "postmessage" }
For my Django server I use Django REST Framework JWT + Django REST Social Auth. Django receives the code from frontend, verify it with Google's service (done for you). Once verified, it'll send the JWT (the token) back to frontend. Frontend can now harvest the token and store it somewhere.
All of REST_SOCIAL_OAUTH_ABSOLUTE_REDIRECT_URI, REST_SOCIAL_DOMAIN_FROM_ORIGIN and REST_SOCIAL_OAUTH_REDIRECT_URI in Django's settings.py are unnecessary. (They are constants used by Django REST Social Auth) In short, you don't have to setup anything related to redirect url in Django. The "redirect_uri": "postmessage" in React frontend suffice. This makes sense because the social auth work you have to do on your side is all Ajax-style POST request in frontend, not submitting any form whatsoever, so actually no redirection occur by default. That's why the redirect url becomes useless if you're using the code + JWT flow, and the server-side redirect url setting is not taking any effect.
The Django REST Social Auth handles account creation. This means it'll check the google account email/last first name, and see if it match any account in database. If not, it'll create one for you, using the exact email & first last name. But, the username will be something like youremailprefix717e248c5b924d60 if your email is youremailprefix#example.com. It appends some random string to make a unique username. This is the default behavior, I believe you can customize it and feel free to dig into their documentation.
The frontend stores that token and when it has to perform CRUD to the backend server, especially create/delete/update, if you attach the token in your Authorization header and send request to backend, Django backend will now recognize that as a login, i.e. authenticated user. Of course, if your token expire, you have to refresh it by making another request.
Oh my goodness, I've spent more than 6 hours and finally got this right! I believe this is the 1st time I saw this postmessage thing. Anyone working on a Django + DRF + JWT + Social Auth + React combination will definitely crash into this. I can't believe none of the article out there mentions this except answers here. But I really hope this post can save you tons of time if you're using the Django + React stack.
In my case, my credential Application type is "Other". So I can't find Authorized redirect URIs in the credentials page. It seems appears in Application type:"Web application". But you can click the Download JSON button to get the client_secret.json file.
Open the json file, and you can find the parameter like this: "redirect_uris":["urn:ietf:wg:oauth:2.0:oob","http://localhost"]. I choose to use http://localhost and it works fine for me.
When you register your app at https://code.google.com/apis/console and
make a Client ID, you get a chance to specify one or more redirect
URIs. The value of the redirect_uri parameter on your auth URI has to
match one of them exactly.
Checklist:
http or https?
& or &?
trailing slash(/) or open ?
(CMD/CTRL)+F, search for the exact match in the credential page. If
not found then search for the missing one.
Wait until google refreshes it. May happen in each half an hour if you
are changing frequently or it may stay in the pool. For my case it was almost half an hour to take effect.
for me it was because in the 'Authorized redirect URIs' list I've incorrectly put https://developers.google.com/oauthplayground/ instead of https://developers.google.com/oauthplayground (without / at the end).
The redirect url is case sensitive.
In my case I added both:
http://localhost:5023/AuthCallback/IndexAsync
http://localhost:5023/authcallback/indexasync
If you use this tutorial: https://developers.google.com/identity/sign-in/web/server-side-flow then you should use "postmessage".
In GO this fixed the problem:
confg = &oauth2.Config{
RedirectURL: "postmessage",
ClientID: ...,
ClientSecret: ...,
Scopes: ...,
Endpoint: google.Endpoint,
}
beware of the extra / at the end of the url
http://localhost:8000 is different from http://localhost:8000/
It has been answered thoroughly but recently (like, a month ago) Google stopped accepting my URI and it would not worked. I know for a fact it did before because there is a user registered with it.
Anyways, the problem was the regular 400: redirect_uri_mismatch but the only difference was that it was changing from https:// to http://, and Google will not allow you to register http:// redirect URI as they are production publishing status (as opposed to localhost).
The problem was in my callback (I use Passport for auth) and I only did
callbackURL: "/register/google/redirect"
Read docs and they used a full URL, so I changed it to
callbackURL: "https://" + process.env.MY_URL+ "/register/google/redirect"
Added https localhost to my accepted URI so I could test locally, and it started working again.
TL;DR use the full URL so you know where you're redirecting
2015 July 15 - the signin that was working last week with this script on login
<script src="https://apis.google.com/js/platform.js" async defer></script>
stopped working and started causing Error 400 with Error: redirect_uri_mismatch
and in the DETAILS section: redirect_uri=storagerelay://...
i solved it by changing to:
<script src="https://apis.google.com/js/client:platform.js?onload=startApp"></script>
Rails users (from the omniauth-google-oauth2 docs):
Fixing Protocol Mismatch for redirect_uri in Rails
Just set the full_host in OmniAuth based on the Rails.env.
# config/initializers/omniauth.rb
OmniAuth.config.full_host = Rails.env.production? ? 'https://domain.com' : 'http://localhost:3000'
REMEMBER: Do not include the trailing "/"
None of the above solutions worked for me. below did
change authorised Redirect urls to - https://localhost:44377/signin-google
Hope this helps someone.
My problem was that I had http://localhost:3000/ in the address bar and had http://127.0.0.1:3000/ in the console.developers.google.com
Just make sure that you are entering URL and not just a domain.
So instead of:
domain.com
it should be
domain.com/somePathWhereYouHadleYourRedirect
Anyone struggling to find where to set redirect urls in the new console: APIs & Auth -> Credentials -> OAuth 2.0 client IDs -> Click the link to find all your redirect urls
My two cents:
If using the Google_Client library do not forget to update the JSON file on your server after updating the redirect URI's.
I also get This error Error-400: redirect_uri_mismatch
This is not a server or Client side error but you have to only change by checking that you haven't to added / (forward slash) at the end like this
redirecting URL list ❌:
https://developers.google.com/oauthplayground/
Do this only ✅:
https://developers.google.com/oauthplayground
Let me complete #Bazyl's answer: in the message I received, they mentioned the URI
"http://localhost:8080/"
(which of course, seems an internal google configuration). I changed the authorized URI for that one,
"http://localhost:8080/" , and the message didn't appear anymore... And the video got uploaded... The APIS documentation is VERY lame... Every time I have something working with google apis, I simply feel "lucky", but there's a lack of good documentation about it.... :( Yes, I got it working, but I don't yet understand neither why it failed, nor why it worked... There was only ONE place to confirm the URI in the web, and it got copied in the client_secrets.json... I don't get if there's a THIRD place where one should write the same URI... I find nor only the documentation but also the GUI design of Google's api quite lame...
I needed to create a new client ID under APIs & Services -> Credentials -> Create credentials -> OAuth -> Other
Then I downloaded and used the client_secret.json with my command line program that is uploading to my youtube account. I was trying to use a Web App OAuth client ID which was giving me the redirect URI error in browser.
I have frontend app and backend api.
From my backend server I was testing by hitting google api and was facing this error. During my whole time I was wondering of why should I need to give redirect_uri as this is just the backend, for frontend it makes sense.
What I was doing was giving different redirect_uri (though valid) from server (assuming this is just placeholder, it just has only to be registered to google) but my frontend url that created token code was different. So when I was passing this code in my server side testing(for which redirect-uri was different), I was facing this error.
So don't do this mistake. Make sure your frontend redirect_uri is same as your server's as google use it to validate the authenticity.
The main reason for this issue will only come from chrome and chrome handles WWW and non www differently depending on how you entered your URL in the browsers and it searches from google and directly shows the results, so the redirection URL sent is different in a different case
Add all the possible combinations you can find the exact url sent from fiddler , the 400 error pop up will not give you the exact http and www infromation
Try to do these checks:
Bundle ID in console and in your application. I prefer set Bundle ID of application like this "org.peredovik.${PRODUCT_NAME:rfc1034identifier}"
Check if you added URL types at tab Info just type your Bundle ID in Identifier and URL Schemes, role set to Editor
In console at cloud.google.com "APIs & auth" -> "Consent screen" fill form about your application. "Product name" is required field.
Enjoy :)
I'm writing an app that uses Google API to authenticate with G+ account in our app.
Currently the customer wants on logging out not only revoke access token for our application but also log out from Google Account used to authenticate.
The solution I've come with was:
/**
* Signs the user out.
*/
HeaderCtrl.prototype.signOut = function() {
// this part revokes token
$http.jsonp('https://accounts.google.com/o/oauth2/revoke?token=' +
accessToken, {
params: {
callback: 'JSON_CALLBACK',
format: 'json'
}
}).success( /* Do stuff on success */);
// this part logs out from google account
$http.jsonp('https://accounts.google.com/logout');
};
The second call works but logs an error on response processing:
Refused to execute script from 'https://accounts.google.com/logout' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.
What ways to log out from Google account using AngularJS would you use?
you should not log out your users from Google, they certainly don't want it anyway. Thus, https://accounts.google.com/logout should never be reached.
What you actually want is to make them log out from your website. Revoking the token should be enough for Google authentication's side (your website won't assume the client is logged from to his old token)
Once signOut method is called, just consider he isn't logged any more and end the session in your website.
I think you are tring to do many things that are not related to a "normal logout" experience.
Maybe you should talk to your "customer" to clarify the user stories he/she wants.
A normal logout is one line of js (source) :
gapi.auth.signOut();
If I'm not wrong, what you are doing first in your code (ie revoking the access token), is something that provides an option to remove the association between the account on your app and the google account used for sign-in. As indicated on the link, you must provide this option to the user to follow the g+ developer policies but it's not the same as signing out. Maybe you should try, as a user, these two feature on a site providing a g+ sign-in, such as stack overflow. (Be sure to know your password before revoking the g+ association.)
And for the log out of Google, your app should not do it, and Google should not provide you a way to do it. (And I'd rather think that it's impossible.)
However you can kindly remind your user, after log out, that he/she may need to log out from Google too. Try to log out from stack overflow and look at what happens.
I have a GAE app, with a URL I restrict to admin:
- url: /admin
script: _go_app
login: admin
I want to PUT or POST to this url with another Go program. What code do I need to write for the client to authenticate to GAE and dev_server.py? Is there a more sensible way that just mocking a web-browser and logging in? I don't need to authenticate or authorise other users, just the admin account for that app.
Is this OAuth? OAuth2? OpenID? Federated? Something else?
I realise this is a bit of an awkward question, since I'm not even sure what the right way to ask it is. However I am able to post to (in this example) /admin using a web browser after logging in with my (admin) gmail account. In that case the request (sent by Chrome) contains the cookies: __cfduid, ACSID (and what I think are Google Analytics IDs). Presumably one of those is responsible for my authentication. How do I get one of those?
And as a side question, if someone MITMs my connection (over http), can they hijack my admin session by reusing that cookie?
GAE likes OAuth2
Have a look at goauth2 . It seems to be the canonical OAuth2 library for Go. They provide a fairly comprehensive example at https://code.google.com/p/goauth2/source/browse/oauth/example/oauthreq.go .
With regards to your question "Presumably one of those is responsible for my authentication. How do I get one of those?", they state:
To obtain Client ID and Secret, see the "OAuth 2 Credentials" section under
the "API Access" tab on this page: https://code.google.com/apis/console/
And, finally, my humble opinion on "if someone MITMs my connection (over http), can they hijack my admin session by reusing that cookie?" is that you should never provide any authenticated connection (nor the connection that does the authentication) over plain http. Especially an admin section.
EDIT: To elaborate on the MITM question, make sure you use HTTPS for any login requests and subsequent requests for the same session, and make sure to set Secure and HttpOnly flags on your cookies.
OAuth2 if you want to use Google Accounts.
See here for details: https://developers.google.com/appengine/docs/go/users/overview (this section specifically deals with admin views)
I'm using a very simple GAE instance from a Greasemonkey script. This worked fine for the last months, but now a path is appended to the final 'continue' location, which breaks the login process for me.
The basic workflow, under the assumption that the user is logged into his Google Account, but his token for the GAE instance has timed out:
User opens page A with the GM script installed.
The GM script runs and tries to access the GAE instance with a GM_xmlhttpRequest().
The GAE instance returns "login_needed|<loginurl>". The GM script extracts the loginurl and sets window.location on it.
The user is redirected to the loginurl and eventually back to A. However, this time, actual data is returned by the GM_xmlhttpRequest().
The last step no longer works, as the user is now redirected to the loginurl plus some, which gives a 404 on the target site.
The GAE code is just about half a screen of code. The authentication relevant code is this:
if not users.get_current_user():
self.response.headers['Content-Type'] = 'text/plain'
self.response.out.write('login_needed|'+users.create_login_url(self.request.get('uri')))
The sequence of requests is as follows, all caused by redirects:
GET https://mygaeinstance.appspot.com/?uri=https://targetsite.com/
GET https://www.google.com/accounts/ServiceLogin?service=ah&passive=true&continue=https://appengine.google.com/_ah/conflogin%3Fcontinue%3Dhttps://targetsite.com/<mpl=gm&ahname=MyGAEInstance&sig=<some sig>
GET https://appengine.google.com/_ah/conflogin?continue=https%3A%2F%2Ftargetsite.com%2F&pli=1&auth=<some base64 auth token>
GET https://targetsite.com/_ah/conflogin?state=<some base64 state>
targetsite.com doesn't like that path and as you can see, it wasn't in the initial 'continue' argument passed to appengine.google.com, which was just "https://targetsite.com/". What did I do wrong and how can I fix this?
A recent change to our login flow for App Engine has created an issue whereby a login with a continue URL that's outside the app's own domain will result in an erroneous redirect such as the one you're observing.
We're working on fixing this. In the meantime, a workaround is to set up a redirect handler on your own app. Make that the target of the continue parameter, and have it send a final redirect to your actual target.
This redirect is caused by an expired auth token. To make it work again, you need to invalidate the token on the client, as described here: What is the proper URL to get an Auth Cookie from a GAE based Application