So currently I can login and store my refresh token(if need be I can also store the access token) in a db using Google OAuth2 while using python social auth.
models.py:
from django.db import models
from django.contrib.auth.models import AbstractUser
class Profile(AbstractUser):
refresh_token = models.CharField(max_length=255, default="")
I have a url /calendar that needs to retrieve the Google calendar events of the currently logged user. I also have a regular login that if the user has a refresh token which means that he has linked his google account to his account. How would I use get_events to just give all of the events associated with that account.
from googleapiclient.discovery import build
from google.oauth2.credentials import Credentials
def get_events(request):
print(request.user.refresh_token)
credentials = Credentials(get_access_token(request), scopes=SCOPES)
service = build('calendar', 'v3', credentials=credentials)
google_calendar_events = service.events().list(calendarId='primary', singleEvents=True,
orderBy='startTime').execute()
google_calendar_events = google_calendar_events.get('items', [])
return google_calendar_events
def get_access_token(request):
social = request.user.social_auth.get(provider='google-oauth2')
return social.extra_data['access_token']
views.py
def login(request):
if request.method == 'POST':
form = AuthenticationForm(request.POST)
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username, password=password)
if user:
if user.is_active:
auth_login(request, user)
return redirect('home')
else:
messages.error(request,'User blocked')
return redirect('login')
else:
messages.error(request,'username or password not correct')
return redirect('login')
else:
form = AuthenticationForm()
return render(request, 'registration/login.html',{'form':form})
Traceback:
RefreshError at /calendar
The credentials do not contain the necessary fields need to refresh the access token. You must specify refresh_token, token_uri, client_id, and client_secret.
Request Method: GET
Request URL: http://localhost:8000/calendar
Django Version: 3.2.9
Exception Type: RefreshError
Exception Value:
The credentials do not contain the necessary fields need to refresh the access token. You must specify refresh_token, token_uri, client_id, and client_secret.
credentials.json
{
"web": {
"client_id": "-.apps.googleusercontent.com",
"project_id": "=",
"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": "-",
"redirect_uris": ["http://127.0.0.1:8000/oauth/complete/google-oauth2/", "http://localhost:8000/oauth/complete/google-oauth2/"],
"javascript_origins": ["http://127.0.0.1:8000", "http://localhost:8000"]
}
}
From your following replying,
It's most likely credentials = Credentials(get_access_token(request), scopes=SCOPES)
request.user_refresh_token is the value of the refresh token as well.
I also have a credentials.json file that contains a token_uri, client_id, and client_secret if that is correct which was used for login.
And, your question is to retrieve the access token using the refresh token. In this case, in order to retrieve the access token, how about the following sample script?
Sample script:
If client_id, client_secret, refresh_token and token_uri are put in a variable, you can use the following script.
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
credentials = Credentials(
token=None,
client_id="###", # Please set the cliend ID.
client_secret="###", # Please set client secret.
refresh_token='###', # Please set refresh token.
token_uri="###" # Please set token URI.
)
credentials.refresh(Request())
access_token = credentials.token
print(access_token)
# If you want to use the following script, you can use it.
# service = build('calendar', 'v3', credentials=credentials)
Or, if client_id, client_secret, refresh_token and token_uri are put in a file, you can use the following script.
tokenFile = '###' # Please set the filename with the path.
credentials = Credentials.from_authorized_user_file(tokenFile) # or, Credentials.from_authorized_user_file(tokenFile, scopes)
credentials.refresh(Request())
access_token = credentials.token
print(access_token)
# If you want to use the following script, you can use it.
# service = build('calendar', 'v3', credentials=credentials)
Reference:
google.oauth2.credentials module
Related
I want to authenticate GitHub user to my GitHub application and serve to my local server 127.0.0.1:8000, but I am not able to take tokens.
This is how GitHub is showing authentication.
From GitHub documentation, I am not able to understand the process of authentication after generating private key, then how to create JWT and installation tokens ?
Could someone show me what to do next ?
You can follow "Obtaining an Access Token from a GitHub Application Webhook" (Jerrie Pelser), which itself takes from "JWT RSA & HMAC + ASP.NET Core" from Piotr Gankiewicz
Jerrie mentions as first step to convert your PEM file to XML format.
You can use an online tool or write a class
And you need your GitHub application Id:
You will find in the article the class JwtSecurityTokenHandler used to create the JSON Web Token from the XML key.
In Django:
#api_view(['POST'])
#permission_classes([AllowAny, ])
def authenticate_user(request):
try:
email = request.data['email']
password = request.data['password']
user = User.objects.get(email=email, password=password)
if user:
try:
payload = jwt_payload_handler(user)
token = jwt.encode(payload, settings.SECRET_KEY)
user_details = {}
user_details['name'] = "%s %s" % (
user.first_name, user.last_name)
user_details['token'] = token
user_logged_in.send(sender=user.__class__,
request=request, user=user)
return Response(user_details, status=status.HTTP_200_OK)
except Exception as e:
raise e
else:
res = {
'error': 'can not authenticate with the given credentials or the account has been deactivated'}
return Response(res, status=status.HTTP_403_FORBIDDEN)
except KeyError:
res = {'error': 'please provide a email and a password'}
return Response(res)
TL;DR: Is it OK to not store (Google/Facebook) OAuth2 access tokens, but rather request new ones on every login?
I'll explain:
To add social logins ("Login with Google/Facebook") to a Django app, you have Python Social Auth. The problem with it, at least for me, is that it's much more complicated than I'd like, requires a lot of configurations, creates additional tables in the database, and in general feels like a lot of moving parts I don't understand for a rather simple task.
So, I read a bit about Google's and Facebook's flows, and they're simple enough:
The server has an ID and a secret.
The clients/users have their standard login credentials.
Then:
The server redirects the user to Google/Facebook, and provides its ID and a redirection URI.
After the user has logged in, Google/Facebook redirects them to that URI with a code.
The server sends its ID, secret, and the received code to Google/Facebook, and gets an access token in exchange, which it can now use to make API calls on behalf of the user.
Even the most basic permissions are enough to query Google/Facebook for the user's email, which can then be matched against Django's standard User model (and if it doesn't exist, a password-less user can created).
The access token can now be discarded, as it was only necessary to translate the Google/Facebook login to an actual email (validated by them), which is used as the only user identifier - no more confusion due to different accounts when logging via different social services; no additional tables; no unnecessary complexity.
Here's the code, just to show how little is necessary:
# views.py
def login_view(request):
return render(request, 'login.html', {
'google_url': 'https://accounts.google.com/o/oauth2/v2/auth?' + urllib.parse.urlencode({
'client_id': GOOGLE_ID,
'redirect_uri': request.build_absolute_uri(reverse('oauth_google')),
'scope': 'profile email',
'response_type': 'code',
}),
}) # use this url in the template as a social login
def oauth_google(request):
code = request.GET['code']
response = requests.post('https://www.googleapis.com/oauth2/v4/token', {
'code': code,
'client_id': GOOGLE_ID,
'client_secret': GOOGLE_SECRET,
'redirect_uri': request.build_absolute_uri(reverse('oauth_google')),
'grant_type': 'authorization_code',
}).json()
id_token = response['id_token']
response = requests.get('https://www.googleapis.com/oauth2/v3/tokeninfo', {
'id_token': id_token,
}).json()
email = response['email']
user = User.objects.filter(username=email).first()
if not user:
user = User(username=email)
user.save()
auth.login(request, user)
return redirect('index')
# urls.py
urlpatterns = [
...
path('oauth/google', views.oauth_google, name='oauth_google'),
]
My question is: what am I missing? If it's really that simple, why couldn't I find any answers on StackOverflow / tutorials on the web describing just that?
The only reason I can think of is that access tokens come with an expiration time (anywhere between 1 hour and 60 days); so maybe I'm supposed to keep reusing the same access token as long as it's valid (which will require storing it, and would explain why Python Social Auth needs additional tables). Will Google/Facebook get mad at me for requesting new access tokens too frequently and block me? I couldn't find any mention of this in their documentation.
EDIT
Here's the Facebook code, in case anyone finds these snippets useful:
# views.py
def login_view(request):
return render(request, 'login.html', {
'facebook_url': 'https://www.facebook.com/v3.2/dialog/oauth?' + urllib.parse.urlencode({
'client_id': FACEBOOK_ID,
'redirect_uri': request.build_absolute_uri(reverse('oauth_facebook')),
'scope': 'email',
}),
}) # use this url in the template as a social login
def oauth_facebook(request):
code = request.GET['code']
response = requests.get('https://graph.facebook.com/v3.2/oauth/access_token', {
'code': code,
'client_id': FACEBOOK_ID,
'client_secret': FACEBOOK_SECRET,
'redirect_uri': request.build_absolute_uri(reverse('oauth_facebook')),
}).json()
access_token = response['access_token']
response = requests.get('https://graph.facebook.com/me', {
'access_token': access_token,
'fields': 'email',
}).json()
email = response['email']
user = User.objects.filter(username=email).first()
if not user:
user = User(username=email)
user.save()
auth.login(request, user)
return redirect('index')
# urls.py
urlpatterns = [
...
path('oauth/facebook', views.oauth_facebook, name='oauth_facebook'),
]
I am using django-rest-framework for the REST API. Also, for JSON web token authentication I am using django-rest-framework-jwt. After a successful login, the user is provided with a token. I have found how to verify a token with the api call, but is there any way to validate the token inside a view and get the user of that token, similar to request.user?
I need it to validate inside the consumer when using django-channels:
def ws_connect(message):
params = parse_qs(message.content["query_string"])
if b"token" in params:
token = params[b"token"][0]
# validate the token and get the user object
# create an object with that user
I was about to validate the token and get the user by importing VerifyJSONWebTokenSerializer class.
from rest_framework_jwt.serializers import VerifyJSONWebTokenSerializer
data = {'token': token}
valid_data = VerifyJSONWebTokenSerializer().validate(data)
user = valid_data['user']
Hope this helps any body like me.
Use TokenBackend instead of VerifyJSONWebTokenSerializer
from rest_framework_simplejwt.backends import TokenBackend
token = request.META.get('HTTP_AUTHORIZATION', " ").split(' ')[1]
data = {'token': token}
try:
valid_data = TokenBackend(algorithm='HS256').decode(token,verify=False)
user = valid_data['user']
request.user = user
except ValidationError as v:
print("validation error", v)
I want to login a user using the python-social-auth functionality for Google Plus signin in Django. When logging in from my website, everything works fine and the correct details are added to the database.
However, I want to authenticate from my Android application as well. The user logs in in the application, which then sends the access token to the django API, which handles the login process in the following code, adapted from the documentation:
#csrf_exempt
#serengeti_api_request
#psa('social:complete')
def login_social_token(request, backend):
# Ensure the token has been specified.
token = request.META.get('HTTP_ACCESSTOKEN')
if token is None:
raise SerengetiApiRequestException('Access token is missing!')
# Login the user for this session
user = request.backend.do_auth(token)
if user is None:
raise SerengetiApiRequestException('Could not authenticate user!')
login(request, user)
# Store the email address if one has been specified (e.g. Twitter)
email = request.META.get('HTTP_EMAIL')
if email is not None:
user.email = email
user.save()
# Prepare the parameters to be returned
response = dict({
'id': user.id,
'first_name': user.first_name,
'last_name': user.last_name,
'api_key': request.session.session_key,
})
# Return a 200 status code to signal success.
return HttpResponse(json.dumps(response, indent=4), status=200)
When logging in from the website, the social_auth_usersocialauth table contains:
id | provider | uid | extra_data
==========================================
10 | google-oauth2 | <myemail> | {"token_type": "Bearer", "access_token": "<token>", "expires": 3600}
However, when logging in from the application using the above function, the operation completes ok, but the entry in the table looks like this:
id | provider | uid | extra_data
=========================================
10 | google-oauth2 | <empty> | {"access_token": "", "expires": null}
Also, the auth_user table contains a username like eeed494412obfuscated48bc47dd9b instead of the Google Plus username and the email field is empty.
What am I doing wrong and how can I obtain the same functionality as I get on the website?
I would like to mention that I have implemented Facebook and Twitter authentication from the Android application, which call the above-mentioned function and store the correct details, only Google Plus is causing problems.
Just wanted to share an alternative way of doing this. This example is quite primitive and doesn't cover all cases (e.g. failed authentication). However, it should give enough insight into how OAuth2 authentication can be done.
Obtain CLIENT ID
Obtain a CLIENT ID from OAuth2 service provider (e.g. Google) and configure redirect URLs.
I assume you have already done this.
Create a login / registration link
You need to generate a login / registration link in your view. It should be something like this:
https://accounts.google.com/o/oauth2/auth?response_type=code&client_id={{CLIENT_ID}}&redirect_uri={{REDIRECT_URL}}&scope=email
Replace {{CLIENT_ID}} and {{REDIRECT_URL}} with the details you obtained in the previous step.
Create a new view
In urls.py add something like:
url(r'^oauth2/google/$', views.oauth2_google),
In your views.py create a method:
def oauth2_google(request):
# Get the code after a successful signing
# Note: this does not cover the case when authentication fails
CODE = request.GET['code']
CLIENT_ID = 'xxxxx.apps.googleusercontent.com' # Edit this
CLIENT_SECRET = 'xxxxx' # Edit this
REDIRECT_URL = 'http://localhost:8000/oauth2/google' # Edit this
if CODE is not None:
payload = {
'grant_type': 'authorization_code',
'code': CODE,
'redirect_uri': REDIRECT_URL,
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET
}
token_details_request = requests.post('https://accounts.google.com/o/oauth2/token', data=payload)
token_details = token_details_request.json()
id_token = token_details['id_token']
access_token = token_details['access_token']
# Retrieve the unique identifier for the social media account
decoded = jwt.decode(id_token, verify=False)
oauth_identifier = decoded['sub']
# Retrieve other account details
account_details_request = requests.get('https://www.googleapis.com/plus/v1/people/me?access_token=' + access_token)
account_details = account_details_request.json()
avatar = account_details['image']['url']
# Check if the user already has an account with us
try:
profile = Profile.objects.get(oauth_identifier=oauth_identifier)
profile.avatar = avatar
profile.save()
user = profile.user
except Profile.DoesNotExist:
user = User.objects.create_user()
user.save()
profile = Profile(user=user, oauth_identifier=oauth_identifier, avatar=avatar)
profile.save()
user.backend = 'django.contrib.auth.backends.ModelBackend'
login(request, user)
return redirect('/')
You might need the following imports:
from django.shortcuts import redirect
import jwt # PyJWT==0.4.1
import requests # requests==2.5.0
import json
I have a project (not running actually) with google oauth2 authentication. I leave here my config file so it may be useful to you (I was only using oauth2 so some things may vary):
AUTHENTICATION_BACKENDS = (
'social.backends.google.GoogleOAuth2', # /google-oauth2
'django.contrib.auth.backends.ModelBackend',
)
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = 'your google oauth 2 key'
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = 'your secret google oauth 2 key'
SOCIAL_AUTH_PIPELINE = (
'social.pipeline.social_auth.social_details',
'social.pipeline.social_auth.social_uid',
'social.pipeline.social_auth.auth_allowed',
'social.pipeline.social_auth.associate_by_email',
'social.pipeline.social_auth.social_user',
'social.pipeline.user.get_username',
'social.pipeline.user.create_user',
'social.pipeline.social_auth.associate_user',
'social.pipeline.social_auth.load_extra_data',
'social.pipeline.user.user_details'
)
I attach the view also (note that I'm using django rest framework).
class ObtainAuthToken(APIView):
permission_classes = (permissions.AllowAny,)
serializer_class = AuthTokenSerializer
model = Token
# Accept backend as a parameter and 'auth' for a login / pass
def post(self, request, backend):
if backend == 'auth': # For admin purposes
serializer = self.serializer_class(data=request.DATA)
if serializer.is_valid():
token, created = Token.objects.get_or_create(user=serializer.object['user'])
return Response({'token': token.key})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
# Here we call PSA to authenticate like we would if we used PSA on server side.
user = register_by_access_token(request, backend)
# If user is active we get or create the REST token and send it back with user data
if user and user.is_active:
token, created = Token.objects.get_or_create(user=user)
return Response({'id': user.id, 'name': user.username, 'token': token.key})
else:
return Response("Bad Credentials, check the Access Token and/or the UID", status=403)
#strategy('social:complete')
def register_by_access_token(request, backend):
# This view expects an access_token GET parameter
token = request.GET.get('access_token')
backend = request.strategy.backend
user = backend.do_auth(access_token=token, backend=backend)
if user:
# login(request, user) #Only useful for web..
return user
else:
return None
and in the urls.py:
urlpatterns = patterns(
'',
url(r'^login/(?P<backend>[\w-]+)$', ObtainAuthToken.as_view(), ),
)
Sorry for attaching all this code and not providing a specific answer but more data is needed because the error can come from many sources (bad api keys, bad settings configuration, pipeline..). I hope the code helps.
I finally figured it out myself. According to this article in the Android's Google Plus documentation, I also need to request the plus.profile.emails.read scope when making the request in the Android app. Once I added this, the python-social-auth code managed to store the email properly in the uid fields. This allows it to recognize the same user whether logging in from the website or the app, which is what I needed. Here's the scopes string I use:
String scopes = "oauth2:" + Plus.SCOPE_PLUS_LOGIN + " https://www.googleapis.com/auth/plus.profile.emails.read";
However, the extra_data field still contains the values I mentioned above. I believe this is due to needing to request offline access as well, which would allow Google Plus to pass the missing fields back to python-django-auth. More details can be found here.
I've been running into the same problem. The reason why the extra_fields on your google user isn't being set is because python-social-auth calls the google server to set those things, but if you're calling Google with just an access_token, it won't be enough to get Google to return the refresh_token and all those other auth related fields. You can hack it by setting them manually, but then you'd end up using the same access and refresh tokens as the client. Google recommends that you use the client to generate a new authorization token with whatever scopes you need, and then send that auth token to the server, which then will turn it into an access and refresh token. See here for the details (it's a bit of an involved read): https://developers.google.com/identity/protocols/CrossClientAuth
If you're really committed to doing this in the scope of what python-social-auth does, I'd recommend making a custom auth backend, call it GoogleOAuth2AuthorizationCodeAuth (see here for details).
The lazier and probably easy-to-break and gross way is to post the access_token to my server to sign in as a google user (which you're doing properly, it seems), and then later, get another authorization token from the client in order to post to a separate endpoint, which I'll then handle turning into another Credentials model object that's connected to a user profile.
In DjangoRestFramework:
class GoogleAuthorizationCodeView(APIView):
def post(self, request, format=None):
credentials = flow.step2_exchange(code)
saved_creds = GoogleCredentials.objects.create(credentials=credentials)
return Response(status=status.HTTP_201_CREATED)
I am trying to implement a web service with filtered resources access (OAuth authentication) with Django and I had a couple of questions.
I created two webservers:
http://localhost:8080 : Web Service Provider (using django-piston for the webservice)
http://localhost:8000 : Web Service Consumer
I am trying to use the version 1.0a of OAuth to authenticate the consumer against the provider. The workflow of this protocol is described here.
In a nutshell, here are the different steps (name of the resource exchanged):
The consumer requests a token from the provider (key, secret)
If the consumer is valid, the provider returns it a token (oauth_token, oauth_token_secret)
The consumer redirects the User to the provider to login/grant access (oauth_token)
The user grant access to the consumer for the resource.
The provider gives the consumer a token verifier (token_verifier)
The consumer requests an access_token (key, secret, oauth_token, oauth_token_secret, oauth_verifier)
The provider gives the consumer an access_token (oauth_token)
The consumer uses its oauth_token to access the resource
Here is the code of the views of my consumer:
from django.shortcuts import render_to_response
from django.http import HttpResponse, HttpResponseRedirect
import oauth2 as oauth
import urlparse
REQUEST_TOKEN_URL = 'http://127.0.0.1:8080/api/authentication/request_token/'
AUTHORIZATION_URL = 'http://127.0.0.1:8080/api/authentication/authorize/'
ACCESS_TOKEN_URL = 'http://127.0.0.1:8080/api/authentication/access_token/'
CONSUMER_CALLBACK_URL = 'http://127.0.0.1:8000/request_access_token/'
CONSUMER_KEY = 'key'
CONSUMER_SECRET = 'secret'
consumer = oauth.Consumer(CONSUMER_KEY, CONSUMER_SECRET)
client = oauth.Client(consumer)
def request_token(request):
"""
Contacts the service provider to get a token.
"""
resp, content = client.request(REQUEST_TOKEN_URL, 'GET')
oauth_token = dict(urlparse.parse_qsl(content)).get('oauth_token', None)
oauth_token_secret = dict(urlparse.parse_qsl(content)).get('oauth_token_secret',
None)
if oauth_token is None:
return render_to_response('home.html', {'data': 'NO TOKEN FOUND'})
else:
request.session['oauth_token'] = oauth_token
request.session['oauth_token_secret'] = oauth_token_secret
return HttpResponseRedirect('request_user_permission/')
def request_user_permission(request):
"""
Redirects the user to the service provider to get permission if
token provided.
"""
oauth_token = request.session['oauth_token']
if oauth_token is None:
return render_to_response('home.html', {'data': 'NO TOKEN FOUND'})
else:
return HttpResponseRedirect("%s?oauth_token=%s&oauth_callback=%s"
% (AUTHORIZATION_URL, oauth_token, CONSUMER_CALLBACK_URL))
def request_access_token(request):
"""
Requests an access token from the service provider
if the user granted permission.
"""
error = request.GET.get('error', None)
if error is None:
oauth_verifier = request.GET.get('oauth_verifier', None)
if oauth_verifier is None:
return render_to_response('home.html',
{'data': 'UNKNOWN ERROR HAPPENED'})
else:
# User permission granted, requesting access token
oauth_token = request.session['oauth_token']
oauth_token_secret = request.session['oauth_token_secret']
token = oauth.Token(oauth_token, oauth_token_secret)
token.set_verifier(oauth_verifier)
client.token = token
resp, content = client.request(ACCESS_TOKEN_URL, 'POST')
access_token = dict(urlparse.parse_qsl(content))
return render_to_response('home.html', {'data': access_token})
else:
return render_to_response('home.html', {'data': error})
General OAuth Questions
How many token should the consumer have? One? One per User? One per Resource?
How is the consumer supposed to store its token(s)?
How do you specify which resources the consumer can access with its token? Shouldn't the consumer be able to provide the id of the resource it wants to get access to when redirecting the user to the service provider (step 3)?
If a consumer wants to access a resource for which the user has already granted the access in the past, should it redirect the user to the service provider anyway (and let the service provider return the oauth_verifier instantly, instead of asking the User for permission)?
Technical problems
Right now, I am using local memory cached sessions to store the token, but it doesn't work:
When the sessions are activated on the consumer server, the User has to login every time on the service provider server (even if he was already logged in).
In the first view (requesting the token) I store the oauth_token and oauth_token_secret in the request's session. When I try to access it in the second view (before redirecting the user), it works. But when I try to access it in the last view (after the redirection) it doesn't (KeyError, oauth_token is not found in the request.session dictionary)
Thank you!
I'm in the same-ish boat (implementing an API with OAuth authentication), and I came across the django-oauth-plus project in my research. The tutorial does a good job of walking through each step of the process outlined in the diagram you linked to. The code itself seems a pretty complete implementation of OAuth (not sure if I know what I'm talking about tho, just learning this stuff).
Also, this guy has a pretty great tutorial about the basics of OAuth (I struggled with that).