I have a Flask backend server that integrates Reddit's OAuth integration via PRAW, and a VueJS Frontend.
The backend APIs for handling the OAuth cycle look like this:
#api_user.route('/login', methods=['GET'])
def login() -> Response:
reddit: Reddit = Reddit()
auth_url: str = reddit.auth.url(scopes=SCOPES, state=STATE)
auth_url = urllib.parse.unquote(auth_url) # remove url encodings
return redirect(auth_url)
#api_user.route('/auth_callback', methods=['GET'])
def callback() -> Tuple[Response, int]:
code: str = request.args.get('code', '')
reddit = Reddit()
refresh_token: str = reddit.auth.authorize(code)
me: Redditor = reddit.user.me()
token = jwt.encode({
'refresh_token': refresh_token,
'user': {
'name': me.name,
'id': me.id
}
}, str(SECRET_KEY), algorithm=ALGORITHM)
response: Response = make_response(jsonify({
'token': token
}))
response.set_cookie('access_token', token, max_age=COOKIE_MAX_AGE)
return response, 200
#api_user.route('/me', methods=['GET'])
#jwt_required
def about(reddit: Reddit) -> Tuple[Response, int]:
user: Redditor = reddit.user.me()
return jsonify({
'name': user.name,
'id': user.id,
'karma': user.link_karma + user.comment_karma
}), 201
/api/login redirects to Reddit's authorization URL where user can provide their username/password, which then redirects the callback uri /api/auth_callback (set in the Reddit app configuration), which then generates a refresh token and sends it as a JWT Token to be stored as a cookie.
What I would like to know is how would I connect this flow with a separate VueJS frontend. Even if the VueJS login page /login calls the API /api/login, after the OAuth is finished it will end up at the server-side route /api/auth_callback?code=xyz.
Integrating VueJS in the Jinja2 Template and making the whole application contained within Flask can work, but it becomes messy quickly.
Another way is to make the redirect_uri be a VueJS route, not a backend route. But in that case, I would like to know how can I parse the URL and extract the code parameter (like I did in the /auth_callback Flask route handler). In that case, I can get rid of /api/auth_callback and add /api/jwt which takes the code parameter extracted by the Vue frontend and returns the JWT token after validating using PRAW.
Any kind of help would be appreciated. Thanks.
Related
I'm trying to create an auth system for my react(nextjs) app with flask_JWT_extended + ariadne(graphql). I have succesfully set up the login mutation that retrieves the access and refresh token but I dont know how to properly integrate them into my app. I am aware the access token is used to make subsequent requests and the refresh token is used to maintain token freshness but I dont know how to implement it with this stack.
mutations.py
Here is my login mutation that returns the access_token and refresh_token. It works fine.
#mutation.field("login")
#convert_kwargs_to_snake_case
def resolve_login(_, info, username, password):
user = User.query.filter_by(username=username).first()
if user and user.check_password(password):
access_token = create_access_token(identity=username)
refresh_token = create_refresh_token(identity=username)
payload = {
"user": user,
"access_token": access_token,
"refresh_token": refresh_token,
}
return payload
core.py
Here are my JWT configs, from what I have gathered online I am supposed to do a check on the token on each api request to maintain its freshness but I dont know how to do that especially with python + ariadne. Here is a link with someone implementing it with nodejs: https://github.com/benawad/graphql-express-template/blob/22_advanced_jwt_auth/auth.js
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "postgresql://localhost/pinkle"
app.config["JWT_SECRET_KEY"] = "this_is_a_secret"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
JWTManager(app)
index.js
Here is my front end making the call to login the user, it returns the tokens but I dont know where to utilize the tokens or if I should save it in client side state and just make calls with the token.
function login({ username, password }) {
axios
.post('http://localhost:5000/graphql', {
query: `mutation {
login(username: "${username}", password: "${password}") {
user {
id
username
password
}
}
}`,
})
.then(result => {
console.log(result.data)
})
}
The Flask-JWT-Extended documentation includes examples of utilizing JWTs from JavaScript which might be helpful for you: https://flask-jwt-extended.readthedocs.io/en/stable/token_locations/
I've been trying to use Flask-dance's OAuth2ConsumerBlueprint to make requests to the Fitbit API. So far, I've managed to get the authorization page to come up on my Flask app but I haven't been able to route to a page where I can view the data yet.
In browser, when I try to make a request I get the output of the CURL response {"errors":[{"errorType":"system","fieldName":"n/a","message":"Authorization Error: Invalid authorization token type"}],"success":false}
My goal right now is just to be able to view the user's fitbit dashboard in my app through the API by making a request through the app to "https://api.fitbit.com/1/user/-/profile.json"
This is my code so far. If anyone could offer some guidance as to where I'm going wrong with the Oauth2.0 authorization code flow, flask, or the fitbit api I would greatly appreciate it.
from flask import Flask, redirect, url_for, render_template
from flask_dance import OAuth2ConsumerBlueprint
import os
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
CLIENT_ID = 'CLIENT_ID' # OAuth 2.0 Client ID
CLIENT_SECRET = CLIENT_SECRET'
scope = ["activity",
"nutrition",
"heartrate",
"location",
"nutrition",
"profile",
"settings",
"sleep",
"social",
"weight",
]
# Flask OAuth2 Custom Blueprint for Fitbit API
app = Flask(__name__)
fitbit_blueprint = OAuth2ConsumerBlueprint(
"fitbit-api", __name__,
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
base_url="https://www.fitbit.com",
token_url="https://api.fitbit.com/oauth2/token",
authorization_url="https://www.fitbit.com/oauth2/authorize",
scope=scope
)
app.register_blueprint(fitbit_blueprint, url_prefix="/login")
app.secret_key = "supersecret" # Replace this
app.token = fitbit_blueprint.token
print(app.token)
#app.route("/")
def index():
#return redirect(url_for("fitbit-api.login"))
return render_template('index.html')
#app.route("/success")
def access():
return "Success"
#app.route('/login')
def login():
return redirect(url_for("fitbit-api.login"))
# Redirect URI = http://127.0.0.1:
if __name__ == '__main__':
app.run(host="localhost", port=5000, debug=True)
You can not access the (user profile) resource before you get authorized from fitbit. Once you got authorized, you will need to exchange your authorization code with a Token Pair and save the tokens - namely Access Token and Refresh Token - somewhere in your code.
Solely being able to reach and pass the authorization page does not mean that you've been authorized. You can check if your authorization is completed within your fitbit profile:
Open your Fitbit profile -> My Dashboard -> Settings. From the left panel choose Applications. There you should be able to see a list of authorized apps: If you don't, you haven't changed your authorization code with a token pair yet!
You can only then be able to make requests to the user-profile endpoint in which case you have to build up an http post with your valid saved Access Token in its header as in https://dev.fitbit.com/build/reference/web-api/oauth2/#making-requests.
I'm trying to create a login system using Flask and HTTP Basic Auth. My question is, is it my responsibility to provide user information from databases, or does basicauth create and access those databases for me? If it doesn't, what can I use to do that?
Werkzeug can decode the Basic Authorization header for you, into the username and password. The rest is up to you to see what you want to do with that information.
The request.authorization attribute returns a Authorization object. For basic authentication headers, only username and password are set.
A project like Flask-Login can help you manage more complex logins with Basic Authorization, and tie that in with a user model you provide. That model can be stored in a database or anything else you so desire.
And you can look at Flask-Security for a more fully integrated security package that uses Flask-Login and other packages to provide Basic Authentication and session based logins.
The Flask-HTTPAuth extension (shameless plug, I'm the author) simplifies the implementation of HTTP Basic Auth. Instead of working with the request.authorization data directly you set up callback functions where you plug the authentication logic.
Regarding your database question, Flask-HTTPAuth makes no assumptions about how your users are stored. You have to provide the logic that retrieves users and validates passwords.
Werkzeug parses the Authorization header into request.authorization, which is an Authorization object.
For security reasons, a browser might only send this header if it first received a 401 error response with a WWW-Authenticate header set. A different client, such as the requests library, will send the header directly.
The simplest demonstration of this is a decorator that checks request.authorization and returns a 401 response if it's not set, or if the credentials were invalid. In practice, you should use an extension such as Flask-Login or Flask-HTTPAuth to manage this.
from functools import wraps
from flask import request
def login_required(f):
#wraps(f)
def wrapped_view(**kwargs):
auth = request.authorization
if not (auth and check_auth(auth.username, auth.password)):
return ('Unauthorized', 401, {
'WWW-Authenticate': 'Basic realm="Login Required"'
})
return f(**kwargs)
return wrapped_view
#app.route('/secret')
#login_required
def secret():
return f'Logged in as {request.authorization.username}.'
import requests
response = requests.get('http://127.0.0.1:5000/secret', auth=('world', 'hello'))
print(response.text)
# Logged in as world.
Here's a Flask Basic authentication example using Python decorator function.
It will return 401, Authentication required if not auth or wrong auth.
Flask API
def check_auth(username, password):
return username == 'username' and password == 'password'
def login_required(f):
""" basic auth for api """
#wraps(f)
def decorated_function(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
return jsonify({'message': 'Authentication required'}), 401
return f(*args, **kwargs)
return decorated_function
#app.route('/', methods=["GET"]
#login_required
def home():
return {"Hello": "world"}
While requesting on server
response = requests.get(url, auth=('username', 'password'))
This question already has answers here:
Return JSON response from Flask view
(15 answers)
Closed 5 years ago.
I am using React as frontend and Python & Flask as my backend with Rauth as OAuth2 for Facebook login and storing details of every user in db and using flask_login as protection. Those information are Sports, Education, Phone number and other things.
What I am doing is after user gets logged in I am trying to fetch his details from react app. But I am getting error user not logged in while on browser if I open that API I can see the json.
#login_manager.user_loader
def load_user(id):
return User.select().where(User.id == id).first()
#app.route('/')
def index():
return redirect(WEB_URL)
#app.route('/logout')
#login_required
def logout():
logout_user()
return redirect(WEB_URL)
#app.route('/me')
#login_required # After removing decorator still I am not getting
def me():
return jsonify({'email': current_user.email}), 200
#app.route('/authorize/<provider>')
def oauth_authorize(provider):
if not current_user.is_anonymous:
return redirect(WEB_URL)
oauth = OAuthSignIn.get_provider(provider)
return oauth.authorize()
#app.route('/callback/<provider>')
def oauth_callback(provider):
if not current_user.is_anonymous:
return redirect(WEB_URL)
oauth = OAuthSignIn.get_provider(provider)
social_id, username, email = oauth.callback()
if social_id is None:
return redirect(WEB_URL)
user = User.select().where(User.social_id == social_id).first()
if not user:
user = User(social_id=social_id, nickname=username, email=email)
user.save()
login_user(user, True)
return redirect(WEB_URL)
So from fronend I am calling /authorize/<provider> which then redirects to /callback/<provider> and logs user in.
Now When I am opening /me on browser it works fine returns required json or redirects me to homepage.
But when I am trying to fetch the api from frontend or Postman either I am getting user not found (if I remove the decorator login_required) or returning the whole homepage.
I am fetching like this:
componentWillMount() {
fetch('http://localhost:5000/me').then(function(response) {
const contentType = response.headers.get("content-type");
if(contentType && contentType.includes("application/json")) {
return response.json();
}
}).then(function(json) {
debugger
const self = this;
if (json) self.serState({ loggedIn: true });
}).catch(function(error) { console.log(error); });
}
My frontend is running on http://localhost:3000/
Got the Answer
Backend add this line after importing flask_cors
CORS(app, supports_credentials=True)
And Fetch using
fetch('http://localhost:5000/me', {
method: 'GET',
credentials: 'include'
})
Install flask restful
https://flask-restful.readthedocs.io/en/latest/
Use rxjs to grab data from the api using a "service"
When I make a redirect from main.py, it works, but when I try to redirect from within a method that it calls, nothing happens. There is no error, the program simply does nothing.
main.py
from githubauth import GetAuthTokenHandler
class AuthUser(webapp2.RequestHandler):
"""
If no environment variable exists with the access token in it,
auth the user as an admin. If it does exist, auth them as a regular
user.
"""
def get(self):
if not ACCESS_TOKEN:
# No access token exists, auth user as admin
get_auth_token = GetAuthTokenHandler()
get_auth_token.get()
githubauth.py
import webapp2
class GetAuthTokenHandler(webapp2.RequestHandler):
"""Redirect users to github to get an access request token."""
def get(self):
self.redirect('http://api.github.com/authorize')
It depends on what kind of authorization you're doing with Github, there are two ways to do that, OAuth token authorization and Web Application Flow.
OAuth Token Authorization
If you're doing OAuth authorization, you don't have to create a request handler to fetch Github auth token, request handler is for serving specific url on your server, for this kind of task, you should use urlfetch().
So the whole flow should be like the following code:
import webapp2
from google.appengine.api import urlfetch
def getAuthToken():
github_auth_url = "http://api.github.com/authorizations"
result = urlfetch.fetch(github_auth_url)
return result
class AuthUser(webapp2.RequestHandler):
def get(self):
if not ACCESS_TOKEN:
# No access token exists, auth user as admin
get_auth_token = getAuthToken()
# do something with your token...
Redirect Authorization (Web Application Flow)
This is the case if you have applied a client id, and want to be authorized by users as a standalone web application, the steps of this kind authorization is more complicated than former one:
Redirect users to request GitHub access
GitHub redirects back to your site
If you don't know about this flow, take a look at Github OAuth - Web Application Flow
Let's see how could we do within Google App Engine
Redirect users to request Github access
This is the part which involved in your sample, simply redirect user to the authorize url with specified parameters
from urllib import urlencode
class AuthUser(webapp2.RequestHandler):
def get(self):
# ... do something ...
# Github configuration
github_client_id = "Your github client id..."
github_redirect_url = "Your url for github redirecting users back to your GAE"
github_scope = "Gtihub scopes...."
github_authorize_url = "http://github.com/login/oauth/authorize"
github_authorize_parameters = {
'client_id': github_client_id,
'redirect_url': github_redirect_url,
'scope': github_scop
}
if not ACCESS_TOKEN:
# if no access_token found on your site, redirect users to Github for authorization
url_to_redirect = "%s?%s" % (github_authorize_url, urlencode(github_authorize_parameters))
self.redirect(url_to_redirect)
Github redirects users back to your site
Github will redirect users back to your site based on the previous parameter redirect_url, so you will have to prepare another request handler for receiving redirection from Github.
(You can do this is the same request handler, but it will mess your code)
The redirection back from the step 1 will contains one parameter, code, you will need it to exchange for an access token.
from urllib import urlencode
class GithubRequestHandler(webapp2.RequestHandler):
def get(self):
# this handler need to be bind to redirect_url
# get authentication code
github_code = self.request.get('code')
# prepare data to exchange access token
github_token_url = "https://github.com/login/oauth/access_token"
github_token_parameters = {
'client_id': 'Your Github client id',
'client_secret': 'Your Github client secret',
'code': github_code}
# exchange access token
data = urlfetch.fetch(github_token_url, payload=urlencode(github_token_parameter), method='POST')
# data will perform in the following form:
# access_token=e72e16c7e42f292c6912e7710c838347ae178b4a&scope=user%2Cgist&token_type=bearer
# extract access_token from the string
# save the access_token to user's model
P.S.
the code is kinda of simulation of your application flow, it needs some tuning to be able to run on production :)
You try to create a webapp2 request handler, but it cannot be done this way. get_auth_token is not a WSGI webapp2 handler instance.
If you do not change githubauth.py you have to change your main.py.
class AuthUser(webapp2.RequestHandler):
def get(self):
if not ACCESS_TOKEN:
self.redirect(to your GetAuthTokenHandler)
This will result in two redirects if you do not have an access token.
RequestHandler needs to be instantiated with a request and a response for things to work properly.
That said, instantiating one and calling methods on it from inside the handler-method of another is pretty weird.