I would like to return the token as a string to another python script. However I'm stuck at the callback page. I made the redirect_uri to "/callback" and it returns the authorization code in the URL as
www.my.website.com/callback?code={code}.
The Facebook OAuth 2 example at http://requests-oauthlib.readthedocs.org/en/latest/examples/facebook.html. I keep getting a 404 error by just pasting the redirect URI.
I would also like to request the following parameters: {id, message, created_time} but I'm not sure how.
import os
from requests_oauthlib import OAuth2Session
from flask import Flask, request, redirect, session, render_template, url_for
from requests_oauthlib.compliance_fixes import facebook_compliance_fix
app = Flask(__name__)
app.secret_key = os.urandom(24)
#app.route("/login")
def demo():
client_id = '1624399237811291'
client_secret = 'SECRET'
# OAuth endpoints given in the Facebook API documentation
authorization_base_url = 'https://www.facebook.com/dialog/oauth'
token_url = 'https://graph.facebook.com/oauth/access_token'
redirect_uri = 'http://www.bbg.mybluemix.net/callback' # Should match Site URL
facebook = OAuth2Session(client_id, redirect_uri=redirect_uri)
facebook = facebook_compliance_fix(facebook)
# Redirect user to Facebook for authorization
authorization_url, state = facebook.authorization_url(authorization_base_url)
authurl = '%s' % authorization_url
return render_template('welcome.html', authurl=authurl)
# Get the authorization verifier code from the callback url
#app.route("/callback", methods=["GET"])
def callback():
# Fetch the access token
redirect_response = ' '
token = facebook.fetch_token(token_url, client_secret=client_secret,
authorization_response=redirect_response)
return '%s' % token
# Fetch a protected resource, i.e. user profile
#r = facebook.get('https://graph.facebook.com/me?')
#return '%s' % r.content
Related
I know how to create tokens with this library, and also how to put tokens in reponse body:
access_token = create_access_token(identity = token_identity)
refresh_token = create_refresh_token(identity = token_identity)
set_access_cookies({"login": True}, access_token)
set_refresh_cookies({"login": True}, refresh_token)
However, when using it with my flask application, nothing is stored in my browser cookies.
Do I need to do something more than use the set_access_cookies or set_refresh_cookies in order to store tokens in cookies?
One example of code:
import logging
from flask import Blueprint, render_template, redirect, url_for, request, current_app as app, jsonify
from flask_login import login_user, logout_user, login_required
from werkzeug.security import generate_password_hash, check_password_hash
from .models import User
from . import db, login_manager, login_serializer, jwt
from flask_jwt_extended import (create_access_token,
create_refresh_token, jwt_required, jwt_refresh_token_required, get_jwt_identity, get_raw_jwt, set_access_cookies,
set_refresh_cookies, unset_jwt_cookies)
def set_response_cookies(token_identity, resp=None, token_types=["access", "refresh"]):
"""
Helper function to set cookies
"""
logging.warning("Setting cookies")
token_types.sort()
if token_types == ["access", "refresh"]:
access_token = create_access_token(identity = token_identity)
refresh_token = create_refresh_token(identity = token_identity)
if not resp:
resp = jsonify({"access_token": access_token, "refresh_token": refresh_token})
set_access_cookies(resp, access_token)
set_refresh_cookies(resp, refresh_token)
return resp
elif token_types == ["access"]:
access_token = create_access_token(identity = token_identity)
if not resp:
resp = jsonify({"access_token": access_token})
set_access_cookies(resp, access_token)
return resp
elif token_types == ["refresh"]:
refresh_token = create_refresh_token(identity = token_identity)
if not resp:
resp = jsonify({"refresh_token": refresh_token})
set_refresh_cookies(resp, refresh_token)
return resp
else:
raise ValueError("Wrong Call to this function")
#auth.route('/signup', methods=['POST'])
def signup_post():
email = request.form.get('email')
name = request.form.get('name')
password = request.form.get('password')
user = User.objects(email=email)
if user: # Email already exist.
return redirect(url_for('auth.signup')), 409
logging.warning("User not existing")
new_user = User(email=email, name=name, password=generate_password_hash(password, method='sha256'))
new_user.save()
set_response_cookies(email, ["access", "refresh"])
return redirect(url_for('auth.login')), 200
You can make use of set_cookie()
Store token in cookie:
from flask import make_response
#app.route('/')
def index():
response = make_response(render_template(...))
response.set_cookie('access_token', 'YOUR_ACCESS_TOKEN')
response.set_cookie('refresh_token', 'YOUR_REFRESH_TOKEN')
return response
To Retrieve the token from cookie:
from flask import request
#app.route('/')
def index():
access_token = request.cookies.get('access_token')
In this way you can store the token in a cookie and retrive the token from the cookie.
Check that you have several Set-Cookie in your response header:
The HTTP header Set-Cookie is a response header and used to send cookies from the server to the user agent.
I can also suggest to check your app configurations. Here are my configurations related to jwt-extended:
JWT_SECRET_KEY = os.getenv("JWT_SECRET_KEY", 'local-secret')
JWT_TOKEN_LOCATION = ['cookies']
JWT_ACCESS_TOKEN_EXPIRES = datetime.timedelta(seconds=1800)
JWT_COOKIE_SECURE = False
JWT_REFRESH_TOKEN_EXPIRES = datetime.timedelta(days=15)
JWT_COOKIE_CSRF_PROTECT = True
JWT_ACCESS_CSRF_HEADER_NAME = "X-CSRF-TOKEN-ACCESS"
JWT_REFRESH_CSRF_HEADER_NAME = "X-CSRF-TOKEN-REFRESH"
The problem may be client-side instead of server-side. My app uses axios and in order to get it to work for me I had to add axios.defaults.withCredentials = true after importing it in the javascript file:
import axios from 'axios'
axios.defaults.withCredentials = true
With flask_jwt_extended, whenever I'm trying to send a POST request with the following decorators:
#jwt_refresh_token_required
#jwt_required
I am having this 401 error:
{
"msg": "Missing CSRF token"
}
When I use a GET instead, it's working fine.
I have read the documentation that talk about double submit protection, but that does not solve my problem. Any ideas how I could fix my issue?
The code to reproduce the problem is below.
Below is the structure of my code:
- src/__init.py__ # where I put all configs
- src/auth.py # where are the endpoints
init.py
login_serializer = URLSafeTimedSerializer(SERIALIZER_SECRET_KEY)
jwt = JWTManager()
def create_app():
app = Flask(__name__)
app.config["SECRET_KEY"] = SERIALIZER_SECRET_KEY
app.config['JWT_SECRET_KEY'] = JWT_SECRET_KEY
app.config['JWT_TOKEN_LOCATION'] = ['cookies']
app.config['JWT_COOKIE_CSRF_PROTECT'] = True
db.init_app(app)
jwt.init_app(app)
# blueprint for auth routes in our app
from .auth import auth as auth_blueprint
app.register_blueprint(auth_blueprint)
# blueprint for non-auth parts of app
from .routes import main as main_blueprint
app.register_blueprint(main_blueprint)
return app
auth.py
import logging
from flask import Blueprint, request, current_app as app, jsonify
from werkzeug.security import generate_password_hash, check_password_hash
from . import login_serializer, jwt
from flask_jwt_extended import (jwt_required, jwt_refresh_token_required,
get_jwt_identity, get_raw_jwt, unset_jwt_cookies,
current_user, create_access_token, create_refresh_token, set_access_cookies, set_refresh_cookies)
auth = Blueprint('auth', __name__)
def set_response_cookies(token_identity, resp=None, token_types=["access", "refresh"]):
"""
Helper function to set cookies in response
"""
logging.warning("Setting cookies")
resp = jsonify(resp)
token_types.sort()
if token_types == ["access", "refresh"]:
access_token = create_access_token(identity = token_identity)
refresh_token = create_refresh_token(identity = token_identity)
if not resp:
resp = jsonify({"access_token": access_token, "refresh_token": refresh_token})
set_access_cookies(resp, access_token)
set_refresh_cookies(resp, refresh_token)
return resp
elif token_types == ["access"]:
access_token = create_access_token(identity = token_identity)
if not resp:
resp = jsonify({"access_token": access_token})
set_access_cookies(resp, access_token)
return resp
elif token_types == ["refresh"]:
refresh_token = create_refresh_token(identity = token_identity)
if not resp:
resp = jsonify({"refresh_token": refresh_token})
set_refresh_cookies(resp, refresh_token)
return resp
else:
raise ValueError("Wrong Call to this function")
#jwt.user_claims_loader
def add_claims_to_access_token(identity):
"""
"""
return {
'email': identity
}
#jwt.user_loader_callback_loader
def user_loader_callback(identity):
"""
Ignore Here, but I use it to get a User object (not mentionned here) from a Token.
"""
return User.objects(
email=identity,
).first()
#auth.route('/token', methods=['POST'])
def token_post():
""" obj =
{"email": "email", "password": "password"} => Tokens
"""
obj = request.get_json()
resp = set_response_cookies(obj["email"], {"token": True}, ["access", "refresh"])
return resp, 200
#auth.route('/token/access', methods=['POST'])
#jwt_refresh_token_required
def refresh_access_cookies():
if current_user:
resp = set_response_cookies(current_user.email, {"token_refreshed": True}, ["access"])
return resp, 200
So, here, all I have to do to reproduce the error is:
Make a POST request to /token => In postman, my response will get all cookies and headers.
Make a POST request to /token/access => Give the error mentioned above.
On your configuration, you enabled JWT_COOKIE_CSRF_PROTECT to true.
For devep purpose, the error will be gone if you can set it to False which may not safe.
On production, You need to pass csrf_token on your request header.
I think this links can help you.
https://flask-jwt-extended.readthedocs.io/en/stable/tokens_in_cookies/ (see the last section)
https://flask-wtf.readthedocs.io/en/stable/csrf.html
I have been looking for an explanation on how to do this using this particular set of libraries and couldn't really find anything simple and straightforward enough. Since I have figured out a working way myself, I decided to drop it here for people who might be also be looking for this (other beginners like me).
Please feel free to suggest a better solution if one comes to mind!
from flask import Flask, flash, redirect, render_template, request, session, url_for
from twython import Twython, TwythonAuthError, TwythonError, TwythonRateLimitError
from flask_session import Session
from tempfile import mkdtemp
# configure application
app = Flask(__name__)
app.config["SESSION_FILE_DIR"] = mkdtemp()
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"
Session(app)
app.config['SECRET_KEY'] = 'your app secret key'
APP_KEY = 'Your Twitter App Key'
APP_SECRET = 'Your Twitter App Secret'
#app.route('/', methods=["GET", "POST"])
def index():
if request.method == 'POST':
twitter = Twython(APP_KEY, APP_SECRET)
auth = twitter.get_authentication_tokens(callback_url='http://yourwebsite.com/login')
session['OAUTH_TOKEN'] = auth['oauth_token']
session['OAUTH_TOKEN_SECRET'] = auth['oauth_token_secret']
return redirect(auth['auth_url'])
else:
return render_template('index.html')
#app.route('/login')
def login():
oauth_verifier = request.args.get('oauth_verifier')
OAUTH_TOKEN=session['OAUTH_TOKEN']
OAUTH_TOKEN_SECRET=session['OAUTH_TOKEN_SECRET']
twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET)
final_step = twitter.get_authorized_tokens(oauth_verifier)
session['OAUTH_TOKEN'] = final_step['oauth_token']
session['OAUTH_TOKEN_SECRET'] = final_step['oauth_token_secret']
OAUTH_TOKEN = session['OAUTH_TOKEN']
OAUTH_TOKEN_SECRET = session['OAUTH_TOKEN_SECRET']
session['twitter'] = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET)
flash("You've logged in!")
return redirect('/')
When website is accessed via link, render_template('index.html') renders a dynamic site that contain a "Log In with Twitter" button (form with a POST method) if user not logged in, or content otherwise. At the end of the login route I create a Twython session variable for application wide use to access Twitter data, and then redirect user back to index page (to clear the url that contains authentifier parameters). That's it, this works for me well as of now.
I have a Django-rest-framework viewset/router to define an API endpoint. The viewset is defined as such:
class DocumentViewSet(viewsets.ModelViewSet):
permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
model = Document
And the router is defined as
router = DefaultRouter()
router.register(r'documents', viewsets.DocumentViewSet)
with url pattern url(r'^api/', include(router.urls))
I can hit this endpoint in the browser/through curl just fine by getting the right access token and using it for authorization. However, it's not clear how to write tests against this endpoint.
Here is what I've tried:
class DocumentAPITests(APITestCase):
def test_get_all_documents(self):
user = User.objects.create_user('test', 'test#test.com', 'test')
client = APIClient()
client.credentials(username="test", password="test")
response = client.get("/api/documents/")
self.assertEqual(response.status_code, 200)
This fails with an HTTP 401 response from the client.get() call. What is the right way to test an API endpoint in DRF using django-oauth-toolkit for oauth2 authentication?
When you are writing tests, you should aim to extract anything you are not testing from the test itself, typically putting any setup code in the setUp method of the test. In the case of API tests with OAuth, this usually includes the test user, OAuth application, and the active access token.
For django-oauth-toolkit, and other Django applications, I would always recommend looking at the tests to see how they do it. This allows you to avoid making unneeded API calls, especially for multi-part processes like OAuth, and only create the few model objects that are required.
def setUp(self):
self.test_user = UserModel.objects.create_user("test_user", "test#user.com", "123456")
self.application = Application(
name="Test Application",
redirect_uris="http://localhost",
user=self.test_user,
client_type=Application.CLIENT_CONFIDENTIAL,
authorization_grant_type=Application.GRANT_AUTHORIZATION_CODE,
)
self.application.save()
def test_revoke_access_token(self):
from datetime import datetime
from django.utils import timezone
tok = AccessToken.objects.create(
user=self.test_user, token='1234567890',
application=self.application, scope='read write',
expires=timezone.now() + datetime.timedelta(days=1)
)
From there you just need to authenticate using the token that has been generated. You can do this by injecting the Authorization header, or you can use the force_authenticate method provided by Django REST Framework.
I have used the same library for OAuth2,
This worked for me
from oauth2_provider.settings import oauth2_settings
from oauth2_provider.models import get_access_token_model,
get_application_model
from django.contrib.auth import get_user_model
from django.utils import timezone
from rest_framework.test import APITestCase
Application = get_application_model()
AccessToken = get_access_token_model()
UserModel = get_user_model()
class Test_mytest(APITestCase):
def setUp(self):
oauth2_settings._SCOPES = ["read", "write", "scope1", "scope2", "resource1"]
self.test_user = UserModel.objects.create_user("test_user", "test#example.com", "123456")
self.application = Application.objects.create(
name="Test Application",
redirect_uris="http://localhost http://example.com http://example.org",
user=self.test_user,
client_type=Application.CLIENT_CONFIDENTIAL,
authorization_grant_type=Application.GRANT_AUTHORIZATION_CODE,
)
self.access_token = AccessToken.objects.create(
user=self.test_user,
scope="read write",
expires=timezone.now() + timezone.timedelta(seconds=300),
token="secret-access-token-key",
application=self.application
)
# read or write as per your choice
self.access_token.scope = "read"
self.access_token.save()
# correct token and correct scope
self.auth = "Bearer {0}".format(self.access_token.token)
def test_success_response(self):
url = reverse('my_url',)
# Obtaining the POST response for the input data
response = self.client.get(url, HTTP_AUTHORIZATION=self.auth)
# checking wether the response is success
self.assertEqual(response.status_code, status.HTTP_200_OK)
Now everything will work as expected.
Hope this helps. Thanks
from oauth2_provider.models import (
get_access_token_model,
get_application_model,
get_id_token_model,
get_refresh_token_model,
)
class TestOauth(APITestCase):
def setUp(self):
""" create and register user """
self.test_user = User.create..
def test_oauth_application_and_tokens_add(self):
print(self.test_user, self.test_user.id)
"""Applications"""
Application = get_application_model()
app = Application()
app.name = "test"
app.client_type = "confidential"
app.authorization_grant_type = "password"
app.user_id = self.test_user.id
app.save()
# client_id:
print("Application Client ID: ", app.client_id)
# client_secret:
print("Application Client SECRET: ", app.client_secret)
"""Access Token"""
AccessToken = get_access_token_model()
token = AccessToken()
token.user_id = self.test_user.id
token.scope = "read write"
token.expires = timezone.now() + timezone.timedelta(seconds=300)
token.token = "secret-access-token-key"
token.application = app
token.save()
# token
print("Access Token: ", token)
self.auth = "Bearer {0}".format(token.token)
""" ID Token """
IDToken = get_id_token_model()
idt = IDToken()
idt.user_id = self.test_user.id
idt.application = app
idt.expires = timezone.now() + timezone.timedelta(days=10)
idt.scope = "read write"
idt.save()
# id token - returns jti token - successfull
print("ID Token: ", idt)
""" Refresh Token """
RefreshToken = get_refresh_token_model()
refr = RefreshToken()
refr.user_id = self.test_user.id
refr.application = app
refr.token = "statictoken" # The token is issued statically.
refr.access_token = (
token # The access token must not have been used before.
)
refr.revoked = timezone.now() + timezone.timedelta(days=10)
refr.save()
# refresh token
print("Refresh Token: ", refr)
Authentication is failing
I'm trying without success to get my users signed into LinkedIn via Oauth authentication in Python. I'm using Django in python and not using any third party social authentication. I'm using the Guide to gain access to the API using Python and Django. However I am having trouble getting the Access Token. I can get the user logged in and get the Authentication Code. I have placed a new request as the earlier question was far too convoluted. You can see that here: Performing POST on a URL string in Django
Nothing was resolved and still unsure if this is an issue with LinkedIn or the code. LinkedIn have not been particularly helpful here, sadly.
but after getting the Author code, I simply cannot get the access token. I'm getting a 400 error for everything and despite getting the author code, on posting as the documentation suggests, I get the following:
u'oauth_problem=parameter_absent&oauth_parameters_absent=oauth_consumer_key%26oauth_signature%26oauth_signature_method%26oauth_token%26oauth_timestamp%26oauth_verifier'
I'm enclosing my Python code here in its entirety in the hope that someone can spot what is going wrong.
import oauth2 as oauth
import httplib2
import time, os, simplejson
import urllib
import urllib2
import pycurl
from django.http import HttpResponse
from django.http import HttpResponseRedirect
from django.core.urlresolvers import resolve
#from django.core.shortcuts import render, redirect
from django import forms
from django.utils import timezone
import urlparse
import requests
consumer_key = 'Yours'
consumer_secret = 'Yours'
user_token = 'Yours'
user_secret = 'Yours'
consumer = oauth.Consumer(consumer_key, consumer_secret)
access_token = oauth.Token(key=user_token,secret=user_secret)
client = oauth.Client(consumer, access_token)
request_token_url = 'https://api.linkedin.com/uas/oauth/requestToken'
access_token_url = 'https://api.linkedin.com/uas/oauth/accessToken'
authorize_url = 'https://www.linkedin.com/uas/oauth/authenticate'
def login(request):
redirect_uri = urllib2.quote('http://127.0.0.1:9000/loginsuccess')
codeURL = "https://www.linkedin.com/uas/oauth2/authorization?response_type=code&client_id=c3skrqz5wqmm&scope=r_fullprofile&state=DCEEFWF45453sdffef425&redirect_uri=" + redirect_uri
# Fill the keys and secrets you retrieved after registering your app
# Use your API key and secret to instantiate consumer object
#resp,content = client.request("http://api.linkedin.com/v1/people/~?format=json", "GET", "")
#resp, content = client.request(request_token_url, "POST")
#request_token = dict(urlparse.parse_qsl(content))
#return HttpResponse(access_token)
return HttpResponseRedirect(codeURL)
def loginsuccess(request):
authcode = request.GET.get('code')
redirect_uri = 'http://www.jelt.com'
#redirect_succ = 'http://www.127.0.0.1:8080/manage'
postdata = {
'grant_type': 'authorization_code',
'code': authcode,
'redirect_uri': redirect_uri,
'client_id': consumer_key,
'client_secret': consumer_secret,
}
r = requests.post(access_token_url, data=postdata)
#return HttpResponse(r.text)
#return HttpResponse(r.status_code)
return HttpResponseRedirect(redirect_uri)
def Manage(request):
return HttpResponseRedirect('http://www.xyz.com')
def success(request):
redirect_uri = urllib2.quote('http://www.xyz.com')
redirect_uri = "http://www.xyz.com"
return HttpResponseRedirect(redirect_uri)
Your login code is redirecting to the OAuth 2.0 endpoint https://www.linkedin.com/uas/oauth2/authorization but your callback loginsuccess is trying to fetch the OAuth 1.0a token from https://api.linkedin.com/uas/oauth/accessToken. You need to update your access_token_url to the OAuth 2.0 endpoint https://www.linkedin.com/uas/oauth2/accessToken per the LinkedIn docs.