Background:
I'm trying to prototype a quick token-based authentication using Flask-Restful and PyJWT. The idea is that I will have a form with email and password and when user clicks submit, it will generate a token and save it in client side browser and use it in any subsequent requests until the token expires.
Trouble
In my prototype, I was able to create a token using JWT but I don't have an idea of how to pass the JWT into a subsequent request. When I do it in the Postman, it works because I can specify the Authorization header with token in there. But when I login in through UI and token is generated, I do not know how to pass the token generated into a subsequent request (/protected) by making the token persists in the header until it expires. Currently when I login from UI and go to /protected, the Authorization header is missing in /protected header.
Code
class LoginAPI(Resource):
# added as /login
def get(self):
"""
renders a simple HTML with email and password in a form.
"""
headers = {'Content-Type': 'text/html'}
return make_response(render_template('login.html'), 200, headers)
def post(self):
email = request.form.get('email')
password = request.form.get('password')
# assuming the validation has passed.
payload = {
'user_id': query_user.id,
'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=10)
}
token = jwt\
.encode(payload, current_app.config['JWT_SECRET'], current_app.config['JWT_ALGORITHM'])\
.decode('utf-8')
# Is below the right way to set the token into header to be used in subsequent request?
# request.headers.authorization = token
# when {'authorization': token} below as a header, the header only shows up for /login not for any subsequent request.
return make_response({'result': 'success', 'token': token}, 200, {'authorization': token} )
class ProtectedAPI(Resource):
#check_auth
def get(self):
return jsonify({'result': 'success', 'message': 'this is a protected view.'})
# decorator to check auth and give access to /protected
def check_auth(f):
#wraps(f)
def authentication(*args, **kws):
# always get a None here.
jwt_token = request.headers.get('authorization', None)
payload = jwt.decode(jwt_token, 'secret_key', algorithms='HS512'])
# other validation below skipped.
return f(*args, **kws)
return authentication
To persist the access_token you have to store on the client and pass it into your header every time you call your backend and check the authenticity of the token every time.
Related
I am using some basic oauth2 authentication in Python, fast api. I want to hide all of the swagger docs behind a login, and ideally I would want all the login logic to be handled by the oauth2scheme, such that when a user logs in to see the docs, they do not need to reauthenticate in order to test any of the endpoints.
The closest I have gotten to a solution is logging in the user by saving a cookie to the request, then overwriting the swagger ui to check the cookie and logout the user. The only functionality I am missing is having the user be automatically authenticated once entering the docs based on this cookie. Relevant code below:
#dbparse.get("/", include_in_schema=False, response_class=HTMLResponse)
async def login_page(request: Request):
return templates.TemplateResponse("login.html", {"request": request})
#dbparse.post("/login", include_in_schema=False)
async def perform_login(
request: Request,
response: Response,
username: Optional[str] = Form(None),
password: Optional[str] = Form(None),
):
try:
if not (username and password):
return RedirectResponse(
"/?error=true", status_code=status.HTTP_303_SEE_OTHER
)
# Fake an oauth login form to route login attempts to previously written code
form = OAuth2PasswordRequestForm(username=username, password=password, scope="")
token = await generate_token(form)
response = RedirectResponse("/docs", status_code=status.HTTP_302_FOUND)
# Set the user's login cookie so that we can have them auth'd later
response.set_cookie(
key="Authorization",
value=f"{token['token_type'].capitalize()} {token['access_token']}",
)
return response
except:
return RedirectResponse("/?error=true", status_code=status.HTTP_303_SEE_OTHER)
#dbparse.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html(request: Request):
if request.cookies:
try:
token = await generate_token(None, request.cookies)
except:
response = RedirectResponse("/?expired=true", status_code=status.HTTP_303_SEE_OTHER)
response.delete_cookie(key="Authorization")
return response
return get_swagger_ui_html(...)
#dbparse.post("/token", include_in_schema=False)
async def generate_token(form_data: OAuth2PasswordRequestForm = Depends(), cached_token = None):
print("==========================\nGENERATE_TOKEN()\n==========================")
if cached_token:
try:
_, access_token = cached_token["Authorization"].split(" ")
_ = decode_token(access_token)
# Ensure that the token in cookies is working. (Confirmed to work as intended)
return {"access_token": access_token, "token_type": "bearer"}
except:
raise
user = authenticate(form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid username/password",
)
token = create_token(user)
return {"access_token": token, "token_type": "bearer"}
If there is an easier solution, I was unable to find it. The key functionality I am looking for is to use a cookie to authenticate the swagger docs for endpoint use. The endpoints need to require authentication, but ideally when you log in before seeing the docs, this authentication should carry over.
i'm doing my own project.
communicate python program - django server.
first is when program send information about signup(like name, password, id etc.) server return success signal.
next step is when program send login information about sign(like name, password), server return jwt token and program receive jwt token.
I'm try everything what i know... but i don't know how to return jwt token to python program.
any idea?
Assuming you already have a proper way to generate the token correctly:
create an endpoint to login with credentials (note the csrf_exempt to allow POST calls from your program)
path('/login', csrf_exempt(login)) )
create a view to process the request - to protect the credentials, expect them as the payload of a POST request:
#require_POST
def login(request):
username = request.data.get('username', None)
password = request.data.get('password', None)
if username is None:
return HttpResponseBadRequest('username is missing')
if password is None:
return HttpResponseBadRequest('password is missing')
# validate the user/credentials
your_function_to_validate(username, password)
jwt = your_function_to_generate_and_save_the_JWT(username, password)
return HttpResponse(jwt)
call the endpoint using the Python program:
url = base_url + '/login'
credentials = {
'username': 'admin',
'password': '12345'
}
res = post(url, data=credentials)
if res.status_code != 200:
# deal with bad credentials
pass
jwt = res.data
I am not able to figure out how do I continue the request flow after refreshing the expired JWT token in my Flask application.
I am storing access token and refresh token in their respective cookies.
This is how my flow looks like right now:
Below is my decorator function that checks validity of the JWT token
def check_valid_jwt(f):
#wraps(f)
def wrapper():
print(request.cookies)
if 'access_token_cookie' in request.cookies:
print('Verify Signature')
# some code that verifies the JWT signature
print('Signature verification successful')
# some code that extracts claims from the token
if time.time() > claims['exp']:
print('Token is expired')
# some code that get the new access token using refresh token
# What am I supposed to do here after I get the refresh token and continue the request while adding the new token to access_token cookie?
return f()
return wrapper
Here is how my protected endpoint looks like:
#check_valid_jwt
def secretpage():
return render_template("/home/secret_page.html")
After I get the refresh token, I want to continue the flow of the request and add new access token in the cookie but if I add that in check_valid_jwt decorator function, secretpage handler will have no idea that a new access token has been issued.
How do I do it in such a way that if a new access token has been issued, it gets added to the response. Am I complete off here and this is not how Authentication flow works?
The best way is to create a middleware for JWT authentication instead of a decorator
from flask import Flask
class JWTAuthMiddleware(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
access_token = environ.get('HTTP_AUTHORIZATION', None)
# validate access token here and get new one if required by using refresh token
# you can also update the invalid token in the header here if you want
environ['HTTP_AUTHORIZATION'] = 'new access token'
return self.app(environ, start_response)
Now wrap the actual wsgi app with this one
app = Flask(__name__)
app.wsgi_app = JWTAuthMiddleware(app.wsgi_app)
My website uses Django's default auth module to do user authentications. Usually, user just needs to fill out a html form in the login page with his/her username and password and click submit, then the CSRF-protected data will be posted to /auth/login/, the Django auth endpoint.
But now for some reason I also need to do this on my server. This is the same server as the backend authentication server. After researching and trials and errors, I finally got:
from django.views.generic import View
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_protect
import requests
class fb_login(View):
"login view for Facebook OAuth"
#method_decorator(csrf_protect)
def get(self, request):
''' code that gets username and password info. from user's FB profile
is not shown here '''
# login code
url = 'http://localhost:8000/auth/login/'
client = requests.session()
# Retrieve the CSRF token first
client.get(url) # sets cookie
csrftoken = client.cookies['csrftoken']
form = {'username': uname, 'password': pw}
header = {'X-CSRFToken': csrftoken}
resp = requests.post(url, data=form, headers=header)
I also tried to add csrf token as part of the form field:
form = {'username': uname, 'password': pw , 'csrfmiddlewaretoken': csrftoken}
resp = requests.post(url, data=form)
I pretty much just followed the Django doc. But I still get the 403 error saying CSRF verification failed, CSRF cookie not set. I wonder if I missed something here? I've double-checked the process against the Django doc but I cannot find anything that might be wrong or missing.
As suggested by #rodrigo, cookies are also needed to pass the CSRF security check. Here's a simple code snippet:
csrftoken = requests.get(url).cookies['csrftoken']
form = {'username': uname, 'password': pw}
header = {'X-CSRFToken': csrftoken}
cookies = {'csrftoken': csrftoken}
resp = requests.post(url, data=form, headers=header, cookies=cookies)
I'm trying to allow users to login to my Flask app using their accounts from a separate web service. I can contact the api of this web service and receive a security token. How do I use this token to authenticate users so that they have access to restricted views?
I don't need to save users into my own database. I only want to authenticate them for a session. I believe this can be done using Flask-Security and the #auth_token_required decorator but the documentation is not very detailed and I'm not sure how to implement this.
EDIT:
Here's a code example:
#main.route("/login", methods=["GET", "POST"])
def login():
payload = {"User": "john", "Password": "password123"}
url = "http://webserviceexample/api/login"
headers = {'content-type': 'application/json'})
#login to web service
r = requests.post(url, headers=headers, json=payload)
response = r.json()
if (r.status_code is 200):
token = response['user']['authentication_token']
# allow user into protected view
return render_template("login.html", form=form)
#main.route('/protected')
#auth_token_required
def protected():
return render_template('protected.html')
Hey there Amedrikaner!
It looks like your use-case is simple enough that we can implement this ourselves. In the code below, I'll be storing your token in the users session and checking in a new wrapper. Let's get started by making our own wrapper, I usually just put these in a wrappers.py file but can you can place it where you like.
def require_api_token(func):
#wraps(func)
def check_token(*args, **kwargs):
# Check to see if it's in their session
if 'api_session_token' not in session:
# If it isn't return our access denied message (you can also return a redirect or render_template)
return Response("Access denied")
# Otherwise just send them where they wanted to go
return func(*args, **kwargs)
return check_token
Cool!
Now we've got our wrapper implemented we can just save their token to the session. Super simple. Let's modify your function...
#main.route("/login", methods=["GET", "POST"])
def login():
payload = {"User": "john", "Password": "password123"}
url = "http://webserviceexample/api/login"
headers = {'content-type': 'application/json'})
#login to web service
r = requests.post(url, headers=headers, json=payload)
response = r.json()
if (r.status_code is 200):
token = response['user']['authentication_token']
# Move the import to the top of your file!
from flask import session
# Put it in the session
session['api_session_token'] = token
# allow user into protected view
return render_template("login.html", form=form)
Now you can check the protected views using the #require_api_token wrapper, like this...
#main.route('/super_secret')
#require_api_token
def super_secret():
return "Sssshhh, this is a secret"
EDIT
Woah! I forgot to mention you need to set your SECRET_KEY in your apps config.
Just a config.py file with SECRET_KEY="SOME_RANDOM_STRING" will do. Then load it with...
main.config.from_object(config)