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'))
Related
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.
How can i perform basic authentication in bottle framework? in flask i used to:
def check( username, password ):
# This function is called to check if a username/password combination is valid
return username == 'nikos' and password == '******'
def authenticate():
# Sends a 401 response that enables basic auth
return Response( 'Credentials of a registered user required!', 401, {'WWW-Authenticate': 'Basic realm="User!"'} )
and called as:
auth = request.authorization
if not auth or not counters.check( auth.username, auth.password ):
return counters.authenticate()
How can i achieve the same in Bottle framework?
As reported here, Bottle natively contains a decorator that makes Basic Auth pretty straightforward:
from bottle import auth_basic, request, route
def is_authenticated_user(user, password):
# You write this function. It must return
# True if user/password is authenticated, or False to deny access.
#route('/')
#auth_basic(is_authenticated_user)
def home():
return ['hooray, you are authenticated! your info is: {}'.format(request.auth)]
Adapted from ron rothman with a basic auth solution using werkzeug.
from bottle import auth_basic, request, route
from werkzeug.security import generate_password_hash, check_password_hash
users = {'user1': generate_password_hash('pwd!')}
def is_authenticated_user(user, password):
# You write this function. It must return
# True if user/password is authenticated, or False to deny access.
return user in users and check_password_hash(users[user], password)
#route('/')
#auth_basic(is_authenticated_user)
def home():
return ['hooray, you are authenticated! your info is: {}'.format(request.auth)]
I am using python flask to build a simple web app, in which user can hit a path say localhost:8000/ and login. if login is successful, another page is displayed, but i want to know how can I redirect to the main page, if the user is already logged in ? for example, if I log in for the first time, I am taken to the main page, and if I open a second tab and again hit the url for login, I am redirected to the main page automatically( much like gmail? ).
class LoginPage(object):
def on_get(self, req, resp, form={}):
For very simple applications HTTP Basic Auth is probably good enough. Flask makes this very easy. The following decorator applied around a function that is only available for certain users does exactly that:
from functools import wraps
from flask import request, Response
def check_auth(username, password):
"""This function is called to check if a username password combination is valid. """
return username == 'admin' and password == 'secret'
def authenticate():
"""Sends a 401 response that enables basic auth"""
return Response(
'Could not verify your access level for that URL.\n'
'You have to login with proper credentials', 401,
{'WWW-Authenticate': 'Basic realm="Login Required"'})
def requires_auth(f):
#wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
return authenticate()
return f(*args, **kwargs)
return decorated
To use this decorator, just wrap a view function:
#app.route('/secret-page')
#requires_auth
def secret_page():
return render_template('secret_page.html')
If you are using basic auth with mod_wsgi you will have to enable auth forwarding, otherwise apache consumes the required headers and does not send it to your application: WSGIPassAuthorization.
I use django, django rest framework and ember.js; my entire application thereforce communicates via ajax.
Authentication is done via oauth2 and a token is send in the headers within every request.
Everythings nice and shiny but file downloads.
At one point users can download a pdf and I don't know how to apply authentication there - because on the file download I cannot send and headers, it's just a link.
I thought of adding SessionAuthentication to that particular rest api call, but the session always flags the incoming user as anyonymous.
How can I force django to create a session on top of the oauth2 token flow?
I tried login(request, user), but it somehow does not kick in.
I ended up with signed tickets, e.g. i send back a token, that is able to bypass auth for a defined timeframe. Therefore the ajax app can first request the token and then fire again a standard get request with the token attached.
Here's the basic idea, that I mixin to views:
class DownloadableMixin():
"""
Manages a ticket response, where a ticket is a signed response that gives a user limited access to a resource
for a time frame of 5 secs.
Therefore, file downloads can request a ticket for a resource and gets a ticket in the response that he can
use for non-ajax file-downloads.
"""
MAX_AGE = 5
def check_ticket(self, request):
signer = TimestampSigner()
try:
unsigned_ticket = signer.unsign(request.QUERY_PARAMS['ticket'], max_age=self.__class__.MAX_AGE)
except SignatureExpired:
return False
except BadSignature:
return False
if self.get_requested_file_name() == unsigned_ticket:
return True
return False
def get_ticket(self):
signer = TimestampSigner()
return signer.sign(self.get_requested_file_name())
def has_ticket(self, request):
return 'ticket' in request.QUERY_PARAMS
def requires_ticket(self, request):
return 'download' in request.QUERY_PARAMS
def get_requested_file_name(self):
raise NotImplementedError('Extending classes must define the requested file name.')
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.