I want to add 'sign-in via GMail' functionality to a website. I create login.html and project.py to process the response.
I add a button to login.html:
function renderButton() {
gapi.signin2.render('my-signin2', {
'scope': 'profile email',
'width': 240,
'height': 50,
'longtitle': true,
'theme': 'dark',
'onsuccess': signInCallback,
'onfailure': signInCallback
});
};
I have a callBack function. In the browser console, I can see that the response contains access_token, id_token (what is the difference?), and my user profile details (name, email, etc) so the request itself must have succeeded, however, error function is called because the response returned by my gconnect handler is 401:
function signInCallback(authResult) {
var access_token = authResult['wc']['access_token'];
if (access_token) {
// Hide the sign-in button now that the user is authorized
$('#my-signin2').attr('style', 'display: none');
// Send the one-time-use code to the server, if the server responds, write a 'login successful' message to the web page and then redirect back to the main restaurants page
$.ajax({
type: 'POST',
url: '/gconnect?state={{STATE}}',
processData: false,
data: access_token,
contentType: 'application/octet-stream; charset=utf-8',
success: function(result)
{
....
},
error: function(result)
{
if (result)
{
// THIS CASE IS EXECUTED, although authResult['error'] is undefined
console.log('Logged in successfully as: ' + authResult['error']);
} else if (authResult['wc']['error'])
{
....
} else
{
....
}//else
}//error function
});//ajax
};//if access token
};//callback
The code that handles the ajax request to Google throws FlowExchangeError when trying to get credentials = oauth_flow.step2_exchange(code) :
#app.route('/gconnect', methods=['POST'])
def gconnect():
if request.args.get('state') != login_session['state']:
response = make_response(json.dumps('Invalid state parameter.'), 401)
response.headers['Content-Type'] = 'application/json'
return response
# Obtain authorization code
code = request.data
try:
# Upgrade the authorization code into a credentials object
oauth_flow = flow_from_clientsecrets('client_secrets.json', scope='')
oauth_flow.redirect_uri = 'postmessage'
##### THROWS EXCEPTION HERE #####
credentials = oauth_flow.step2_exchange(code)
except FlowExchangeError:
response = make_response(
json.dumps('Failed to upgrade the authorization code.'), 401)
response.headers['Content-Type'] = 'application/json'
return response
# Check that the access token is valid.
access_token = credentials.access_token
url = ('https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=%s'
% access_token)
h = httplib2.Http()
result = json.loads(h.request(url, 'GET')[1])
# If there was an error in the access token info, abort.
if result.get('error') is not None:
response = make_response(json.dumps(result.get('error')), 500)
response.headers['Content-Type'] = 'application/json'
return response
# Verify that the access token is used for the intended user.
gplus_id = credentials.id_token['sub']
if result['user_id'] != gplus_id:
response = make_response(
json.dumps("Token's user ID doesn't match given user ID."), 401)
response.headers['Content-Type'] = 'application/json'
return response
# Verify that the access token is valid for this app.
if result['issued_to'] != CLIENT_ID:
response = make_response(
json.dumps("Token's client ID does not match app's."), 401)
print "Token's client ID does not match app's."
response.headers['Content-Type'] = 'application/json'
return response
stored_access_token = login_session.get('access_token')
stored_gplus_id = login_session.get('gplus_id')
if stored_access_token is not None and gplus_id == stored_gplus_id:
response = make_response(json.dumps('Current user is already connected.'),
200)
response.headers['Content-Type'] = 'application/json'
return response
# Store the access token in the session for later use.
login_session['access_token'] = credentials.access_token
login_session['gplus_id'] = gplus_id
# Get user info
userinfo_url = "https://www.googleapis.com/oauth2/v1/userinfo"
params = {'access_token': credentials.access_token, 'alt': 'json'}
answer = requests.get(userinfo_url, params=params)
data = answer.json()
login_session['username'] = data['name']
login_session['picture'] = data['picture']
login_session['email'] = data['email']
output = ''
output += '<h1>Welcome, '
output += login_session['username']
return output
I have checked client_secrets.json I got from Google API, and it seems ok, do I need to renew it?
{"web":{"client_id":"blah blah blah.apps.googleusercontent.com","project_id":"blah","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"blah client secret","redirect_uris":["http://localhost:1234"],"javascript_origins":["http://localhost:1234"]}}
Why does credentials = oauth_flow.step2_exchange(code) fail?
This is my first time implenting this, and I am learning Web and OAuth on-the-go, all the concepts are hard to grasp at once. I am also using the Udacity OAuth course but their code is old and does not work. What might I be missing here?
You need to follow Google Signin for server side apps which describes thoroughly how the authorization code flow works, and the interactions between frontend, backend and user.
On server side, you use oauth_flow.step2_exchange(code) which expects an authorization code whereas you are sending an access token. Sending an access token here is not part of the authorization code flow or one-time-code flow as explained in the link above :
Your server exchanges this one-time-use code to acquire its own access
and refresh tokens from Google for the server to be able to make its
own API calls, which can be done while the user is offline. This
one-time code flow has security advantages over both a pure
server-side flow and over sending access tokens to your server.
If you want to use this flow, you need to use auth2.grantOfflineAccess() in the frontend :
auth2.grantOfflineAccess().then(signInCallback);
so that when the user clicks on the button it will return an authorization code + access token :
The Google Sign-In button provides both an access token and an
authorization code. The code is a one-time code that your server can
exchange with Google's servers for an access token.
You only need the authorization code if you want your server to access Google services on behalf of your user
From this tutorial it gives the following example that should work for you (with some modification on your side) :
<html itemscope itemtype="http://schema.org/Article">
<head>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script src="https://apis.google.com/js/client:platform.js?onload=start" async defer></script>
<script>
function start() {
gapi.load('auth2', function() {
auth2 = gapi.auth2.init({
client_id: 'YOUR_CLIENT_ID.apps.googleusercontent.com',
// Scopes to request in addition to 'profile' and 'email'
//scope: 'additional_scope'
});
});
}
</script>
</head>
<body>
<button id="signinButton">Sign in with Google</button>
<script>
$('#signinButton').click(function() {
auth2.grantOfflineAccess().then(signInCallback);
});
</script>
<script>
function signInCallback(authResult) {
if (authResult['code']) {
// Hide the sign-in button now that the user is authorized, for example:
$('#signinButton').attr('style', 'display: none');
// Send the code to the server
$.ajax({
type: 'POST',
url: 'http://example.com/storeauthcode',
// Always include an `X-Requested-With` header in every AJAX request,
// to protect against CSRF attacks.
headers: {
'X-Requested-With': 'XMLHttpRequest'
},
contentType: 'application/octet-stream; charset=utf-8',
success: function(result) {
console.log(result);
// Handle or verify the server response.
},
processData: false,
data: authResult['code']
});
} else {
// There was an error.
}
}
</script>
</body>
</html>
Note that the answer above suppose that you want to use authorization code flow / one-time code flow as it was what you've implemented server side.
It's also possible to just send the access token as you did (eg leave the client side as is) and remove the "Obtain authorization code" part :
# Obtain authorization code
code = request.data
try:
# Upgrade the authorization code into a credentials object
oauth_flow = flow_from_clientsecrets('client_secrets.json', scope='')
oauth_flow.redirect_uri = 'postmessage'
##### THROWS EXCEPTION HERE #####
credentials = oauth_flow.step2_exchange(code)
except FlowExchangeError:
response = make_response(
json.dumps('Failed to upgrade the authorization code.'), 401)
response.headers['Content-Type'] = 'application/json'
return response
instead :
access_token = request.data
but doing this wouldn't be the authorization code flow / one-time-code flow anymore
You've asked what was the difference between access_token and id_token :
an access token is a token that gives you access to a resource, in this case Google services
an id_token is a JWT token that is used to identify you as a Google user - eg an authenticated user, it's a token usually checked server side (the signature and the fields of the JWT are checked) in order to authenticate a user
The id_token will be useful server-side to identify the connected user. Checkout the Python example in step 7 :
# Get profile info from ID token
userid = credentials.id_token['sub']
email = credentials.id_token['email']
Note that there are other flow where the website send the id_token to the server, the server checks it, and authenticates the user(server doesn't care about access token/refresh token in this flow). In the case of authorization code, flow only the temporary code is shared between frontend and backend.
One more thing is about refresh_token which are token that are used to generate other access_token. Access tokens have limited lifetime (1 hour). Using grantOfflineAccess generate a code that will give you an access_token + refresh_token the first time user authenticates. It belongs to you if you want to store this refresh_token for accessing Google services in the background, it depends on your needs
Related
The Task##
A django application that allows users to sign up and once the user clicks on the account activation link, Zoho CRM is receiving the data and a contact is created in the CRM section.
The Problem
I am currently working on an absolute masterpiece - the ZOHO API.
I am struggling to set up the native Python code that uses POST/GET requests.
Regarding the zcrmsdk 3.0.0, I have completely given up on this solution unless somebody can provide a fully functional example. The support simply blames my code.
The documentation I consulted:
https://www.zoho.com/crm/developer/docs/api/v2/access-refresh.html,
https://www.zoho.com/crm/developer/docs/api/v2/insert-records.html
Since the post request in postman API works fine I do not understand why it does not work in python code
My approach
Generate an self-client API code on: https://api-console.zoho.com/
Insert that code on Postman and retrieve the access or refresh token
Use this access token in an add_user_contact function that is defined in the documentation
It works! Response is success and it is in Zoho CRM
The permsissions scope I am using is: ZohoCRM.modules.contacts.ALL, ZohoCRM.users.ALL, ZohoCRM.modules.deals.ALL, ZohoCRM.modules.attachments.ALL, ZohoCRM.settings.ALL, AAAserver.profile.ALL
Picture of Post Man POST REQUEST
My own Code
def authenticate_crm():
"""
access to response object id:
response_object.get('data')[0].get('details').get('id')
"""
url = 'https://accounts.zoho.com/oauth/v2/token'
headers = {
'Content-Type': 'application/x-www-form-urlencoded'
}
# one time self-client token here -
request_body = {
"code": "1000.aa8abec144835ab79b8f9141fa1fb170.8ab194e4e668b8452847c7080c2dd479",
"redirect_uri": "http://example.com/yourcallback",
"client_id": "1000.H95VDM1H9KCXIADGF05E0E1XSVZKFQ",
"client_secret": "290e505ec52685fa62a640d874e6560f2fc8632e97",
" grant_type": "authorization_code"
}
response = requests.post(url=url, headers=headers, data=json.dumps(request_body).encode('utf-8'))
if response is not None:
print("HTTP Status Code : " + str(response.status_code))
print(response.json())
I am essentially struggling to convert the Postman API request to a Python request to get the token as part of the workflow. What am I doing wrong here?
The documentation states: Note: For security reasons, pass the below parameters in the body of your request as form-data. (access-refresh link) but passing it in postman as form-data breaks the call completely.
According to their own documentation (which is convoluted, contradictory and full of outdated screenshots) the authentication key is needed only once.
Once the request from above runs, I would take the response in the third image and use the refresh key to add the contact.
I am also open to a solution with the SDK 3.0.0, if anybody can help.
I solved it!
I have changed this line:
response = requests.post(url=url, headers=headers, data=json.dumps(request_body).encode('utf-8'))
to this and added some return statement:
payload = '1000.6d9411488dcac999f02304d1f7843ab2.e14190ee4bae175debf00d2f87143b19&' \
'redirect_uri=http%3A%2F%2Fexample.com%2Fyourcallback&' \
'client_id=1000.H95VDM1H9KCXIADGF05E0E1XSVZKFQ&' \
'client_secret=290e505ec52685fa62a640d874e6560f2fc8632e97&'\
'grant_type=authorization_code'
response = requests.request(method="POST", url=url, headers=headers, data=payload)
if response is not None:
print("HTTP Status Code : " + str(response.status_code))
# print(response.text)
print(response.json())
# catch access and refresh token
at = response.json().get('access_token')
rt = response.json().get('refresh_token')
return at, rt
I do not understand why that is different but that fixed it and I could retrieve keys from ZOHO.
I'm currently developing a Django-React web app and using django-rest-framework-simplejwt and dj-rest-auth for authentication.
At first I was storing JWT in frontend cookies (js-cookie) and sending tokens in the headers to get access for restricted endpoints. Since local client cookies are not HttpOnly and after some research I found out that it was not a safe method to store it on the frontend. So I decided not to store them in the client cookies.
It seems like best solution to use HttpOnly cookies, in django settings I declared cookie name as JWT_AUTH_COOKIE = 'myHttpOnlyCookie', so when I make a request from client with username and password to log-in server responses with the cookie that has the access_token.
For the login part, I didn't write any code since dj-rest-auth handles it well so I use their standard loginserializer and view.(https://github.com/jazzband/dj-rest-auth/blob/master/dj_rest_auth/serializers.py). Well maybe I should modify it.
However the problem is I can't add the token in the header of client requests since I'm not storing the token on the client and it is HttpOnly. Well I really don't know how to authenticate the user if I can't send the token in requests.
Once you make a login request to the server, tokens are added to httponly cookie by default. On consecutive requests cookies are sent by default.
Axios request for login.
axios.post('http://localhost:8080/api/auth/login/',
{'email':'test_email', 'password':'test_password'},
{withCredentials:true},
{headers:{
'Content-Type': 'application/json',
'Accept': 'application/json'
}}
)
"withCredentials" must be always set to "true", this will ensure cookies are added to the every request. Once you login, tokens are stored in httponly coolie. For next requests , refer below pseudo code.
const axiosGetReqConfig = {baseURL: '', withCredentials:true, headers:{'Content-Type':'application/json, 'Accept':'application/json'}}
axiosGetReqConfig.get('test/').then(resp => {console.log(resp)}).catch(err => {console.log(err)})
axiosGetReqConfig.interceptors.response.use(
// If request is succesfull, return response.
(response) => {return response},
// If error occured, refresh token and try again.
(error) => {
const originalRequest = error.config;
console.log(originalRequest)
// Make a call to refresh token and get new access token.
axios('http://localhost:8080/api/auth/token/refresh/', {
method:'post',
withCredentials: true
}).then(resp => {
console.log(resp);
}).catch(err => {
// push user to login page.
console.log(err)
})
// Return original request.
return axios.request(originalRequest)
return Promise.reject(error)
}
)
In the above code, I am creating config object using some basic details and implementing interceptors to refresh token if, access token is expired. If refresh token is expired, user will be re-directed to login page.
Main part with including httponly cookie is the variant that we use in making axios request and "withCredentials". There is an open issue with JWT. Since dj-rest-auth uses JWT, if you need to refresh token, you have to implement middleware in django. Refer below link to implement middleware and add that middleware to settings.
https://github.com/iMerica/dj-rest-auth/issues/97#issuecomment-739942573
How can I refresh the access tokens in google with the refresh token in python ? I have the refresh token, client id and client secret with me
I have tried with the following code for generating the access token (after expiry)
params = {
"grant_type": "refresh_token",
"client_id": client_id,
"client_secret": client_secret,
"refresh_token": refresh_token
}
authorization_url = "https://www.googleapis.com/oauth2/v4/token"
r = requests.post(authorization_url, data=params)
print(r.text)
if r.ok:
return r.json()['access_token']
else:
return None
I got an error in response like this:
{
"error": "invalid_grant",
"error_description": "Bad Request"
}
But I need to generate the access token.
These are the three top reasons for it not to be working. I am not a Python dev so cant test your code sorry.
Send parms as a string
Check your parms make sure they are sent as a single string separated with &.
Post https://accounts.google.com/o/oauth2/token
client_id={ClientId}&client_secret={ClientSecret}&refresh_token=1/ffYmfI0sjR54Ft9oupubLzrJhD1hZS5tWQcyAvNECCA&grant_type=refresh_token
content type header
Also i think you need to set the content type header to something like application/x-www-form-urlencoded.
Change endpoint
If that doesnt work
Also try using https://accounts.google.com/o/oauth2/token instead of https://www.googleapis.com/oauth2/v4/token I remember there being some issue with one or the other of them sometimes
I am trying to hook up to MailChimp's api, in my Django application, to add an email to one of my lists. Seems pretty simple enough. I add my api key to the header of the request, with the email address and other variable in the body of the request. every time I try and connect though, I get a response status code of 400. The message says there is a JSON parsing error, and that my JSON is either formatted incorrectly, or there is missing data required for the request. I am making this same api call however with Postman, and am getting a good response back.
view function
import requests
def join_newsletter(request, email):
# hash the user's email for mailchimp's API
# m = hashlib.md5()
# c_email = email
# m.update(c_email.encode('utf-8'))
# email_hash = m.hexdigest()
api_key = 'apikey ' + settings.MAILCHIMP_API
api_endpoint = api_endpoint
data = {
"email_address": email,
"status": "subscribed"
}
header = {
'Authorization': api_key
}
r = requests.post(api_endpoint, data=data, headers=header)
message = r.content
return message
I want to backup automatically web content from a site which requires login. I try to login by simulating a POST request. But I get the error:
csrf token: CSRF attack detected
Here are some extracts from the code I use:
func postLoginForm(csrfToken string) {
values := make(url.Values)
values.Set("signin[username]", "myusername")
values.Set("signin[password]", "mypassword")
values.Set("signin[_csrf_token]", csrfToken)
resp, err := http.PostForm("https://spwebservicebm.reaktor.no/admin/nb", values)
dumpHTTPResponse(resp) // show response to STDOUT
}
The csrf token I get by fetching the login page and scanning it for a hidden input field named signin[_csrf_token]. The important part of the code for doing that is the following:
// Finds input field named signin[_csrf_token] and returns value as csrfToken
func handleNode(n *html.Node) (csrfToken string, found bool) {
if n.Type == html.ElementNode && n.Data == "input" {
m := make(map[string]string)
for _, attr := range n.Attr {
m[attr.Key] = attr.Val
}
if m["name"] == "signin[_csrf_token]" {
return m["value"], true
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
if csrfToken, found = handleNode(c); found {
return
}
}
return "", false
}
I don't need to be using Go, that is just because I am most familiar with that. Using python could be a solution as well, but I did not have any more luck with that.
The issue is that Go 1.2 does not automatically use a cookie jar for its HTTP requests. The first request is to get the CSRF token from the login page. The second request is to POST a login using that CSRF token. But since no session cookie is attached to the HTTP header on the second request the server does not know that it is the same program trying to login. Thus the server thinks it is a CSRF attempt (that you picked the CSRF token from somewhere else and tried to reuse it).
So to get login page and extract CSRF token, we first create our own client object. Otherwise we have nowhere to attach the cookie jar. http.PostForm does give access to cookie jar:
client = &http.Client{}
Create a cookie Jar described in authenticated http client requests from golang . This was easier to setup and debug than the official: http://golang.org/pkg/net/http/cookiejar/ Cookie Jar
jar := &myjar{}
jar.jar = make(map[string] []*http.Cookie)
client.Jar = jar
resp, err := client.Get("https://spwebservicebm.reaktor.no/admin")
doc, err := html.Parse(resp.Body)
Then to login, we reuse the client object with the cookie jar attached to it:
values := make(url.Values)
values.Set("signin[username]", "myusername")
values.Set("signin[password]", "mypassword")
values.Set("signin[_csrf_token]", csrfToken)
resp, err := client.PostForm("https://spwebservicebm.reaktor.no/admin/login", values)
You'll notice that the code is almost identical to the one in the question except we use client.PostForm instead of http.PostForm.
Thanks to dommage and answer to authenticated http client requests from golang for getting me on right track.
You can scrape the token using beautifulsoup and store it, either in the header or send it to the server. You can do something like this:
from requests import session
from bs4 import BeautifulSoup
def authent(self):
ld('trying to get token...')
r = self.session.get(BASE_URL, headers=FF_USER_AGENT)
soup = BeautifulSoup(r.content)
elgg_token = soup.select('input[name="__elgg_token"]')[0]["value"]
elg_ts = soup.select('input[name="__elgg_ts"]')[0]["value"]
payload["__elgg_token"] = elgg_token # I sent it to the server...
payload["__elgg_ts"] = elg_ts
r = self.session.post(LOGIN_URL, data=payload, headers=FF_USER_AGENT)
if r.url != DASHBOARD_URL:
raise AuthentError("Error")