I am just learning to code and this has been driving me crazy. I don't know if I am missing something very simple or have a total misunderstanding about how authentication works (very possibly both), however, i would appreciate someone help.
I have created a login route for my flask app which renders the following and successfully allows a user logging in.
Login Page
If a user successfully logs in, i want to set the Authorization header using a token generated using JWT. I am using the request libary to do this and get a 200 response but everytime i check the network tab, i can not see the token in the 'Authorization' Response Header.
The idea is that once this Header has been set, i can protect my apis by ensuring a token is present and also use that information to ensure that APIs only return data for that user i.e. decrypt the token to work out the user.
Network tab - No Authorization Header
This is my current code. Where am i going wrong? Does my approach sound correct?
#loginsBlueprint.route('/login', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
user = User.query.filter_by(username=request.form['username']).first()
if user is not None and bcrypt.check_password_hash(user.password, request.form['password']):
token = jwt.encode(
{'user': request.form['username'], 'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=15)},
app.config['SECRET_KEY'])
url = 'http://127.0.0.1:5000/login'
headers = {'Authorization': 'Bearer ' + token}
requests.get(url, headers=headers)
return render_template('landing_page.html')
else:
return make_response('Could not verify', 403,
{'WWW-Authenticate': 'Basic realm ="Wrong Password !!"'})
return render_template('login.html', error=error)
login.html
<html>
<head>
<title>Flask Intro - login page</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="static/bootstrap.min.css" rel="stylesheet" media="screen">
</head>
<body>
<div class="container">
<h1>Please login</h1>
<br>
<form action="" method="post">
<input type="text" placeholder="Username" name="username" value="{{
request.form.username }}">
<input type="password" placeholder="Password" name="password" value="{{
request.form.password }}">
<input class="btn btn-default" type="submit" value="Login">
</form>
{% if error %}
<p class="error"><strong>Error:</strong> {{ error }}
{% endif %}
</div>
</body>
</html>
Note: I have tried changing to requests.post but get a 400:
127.0.0.1 - - [30/Mar/2021 21:53:29] "←[31m←[1mPOST /login HTTP/1.1←[0m" 400
I thought it might be because i'm using CORS and so added this in:
CORS(app, expose_headers='Authorization')
A few things which are confusing here:
In the login() method, you make a requests.get() call. So it looks like you're calling a new endpoint from your app. Is that how it should work? To be exact, you seem to check the user's credentials, issue a JWT and then call the /login endpoint of an app listening on port 5000, setting the JWT as the bearer token.
Authorization is a request header. You don't use it in responses to the client. If you want to return the JWT to the client use one of the OAuth flows, either the Code flow (preferably) or the Implicit flow.
Once you'll get the token to your client, then you should use it as you said - set in the Authorization header as a Bearer token. As you're using flask, you can use this filter to secure your APIs.
Related
I am using OAuth2 to store the authentication JWT access_token for employee login. The code works as intended when performing authentication through the FastAPI/docs page, but fails to do so when the username and password are sent through an HTML Login Form. In both these cases, the /token method properly receives the credentials and fetches (and returns) the access_token from the employee_login function (abstracted away and works as intended).
However, in the case of the HTML form, the oauth2 fails to store the token, and when navigating to /employees I get {"detail":"Not authenticated"}.
Would oAuth2 be the correct approach to use when HTML forms are involved or would a direct cookie-storing option be ideal? If oAuth2 can work in this manner, would you have any input on why the HTML form does not authenticate but going through the /docs page does? Thank you!
FAST API:
oAuth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
#app.get("/")
def employee_landing(request: Request, alert=None) -> dict:
'''Landing Page with auth options'''
return EMPLOYEE_TEMPLATES.TemplateResponse(
"landing.html",
{
"request": request,
"alert":alert
}
)
#app.post("/token")
async def login(request:Request, input_data: OAuth2PasswordRequestForm = Depends()):
''' Login a user '''
login_state = employee_login(input_data.username, input_data.password)
return {"access_token": login_state['token'], "token_type": "bearer"}
#app.get("/employee")
async def get_employee(token: str = Depends(oAuth2_scheme)):
''' Get employee info if logged in'''
return token
Landing Page HTML:
<!-- sign in form -->
<form action="/token" method="post">
<input type="text" name="username" placeholder="username" required>
<input type="password" name="password" placeholder="password" required>
<input type="submit" value="Sign In">
</form>
I have a python flask app with login module implemented using extension python flask. In my login method.
The Error Message
app.py
# import the Flask class from the flask module
from flask import Flask, render_template, redirect, url_for, request, session
# create the application object
app = Flask(__name__)
app.secret_key = "hello"
# use decorators to link the function to a url
##app.route('/')
#def home():
# return "Hello, World!" # return a string
#app.route('/index', methods=['GET'])
def index():
if session.get ('username'):
return render_template('index.html')
else:
return render_template('login.html') # render a template
# route for handling the login page logic
#app.route('/', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
if request.form['username'] != 'admin' or request.form['password'] != 'admin':
error = 'Invalid Credentials. Please try again.'
else:
session['username'] = True
return redirect(url_for('index'))
return render_template('login.html', error=error)
# start the server with the 'run()' method
if __name__ == '__main__':
app.run(debug=True)
login page
login.html
<html>
<head>
<title>Flask Intro - login page</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div class="container">
<h1>Please login</h1>
<br>
<form action="" method="post">
<input type="text" placeholder="Username" name="username" value="{{
request.form.username }}">
<input type="password" placeholder="Password" name="password" value="{{
request.form.password }}">
<input class="btn btn-default" type="submit" value="Login">
</form>
{% if error %}
<p class="error"><strong>Error:</strong> {{ error }}</p>
{% endif %}
</div>
</body>
</html>
index.html
<DOCTYPE Html>
<html>
<head>
<tile>Addressing a Site</tile>
</head>
<body>
<address>welcome.</address>
</body>
</html>
i am trying to create a session between login and index page,just getting started with python flask framework, login session not working well.
It seems like you are actually having an issue with the methods rather than the session not working.
The Method Not Allowed error means that you are sending a GET/POST request to a page that does not accept that type of request.
Try changing the start of your login form to this
<form action="{{ url_for('login') }}" method="POST">
I just got it running on my computer and this works, the reason it did not work before was because the form was sending the post request to the /index route, which only accepts GET request.
Also, I noticed something else you may want to change.
You currently have this on your index route
if session.get ('username'):
return render_template('index.html')
else:
return render_template('login.html')
But it would be better to change that to this
if session.get ('username'):
return render_template('index.html')
else:
return redirect(url_for('login'))
That way the user is sent back to the login page rather than just being shown the login template from the index page.
I hope all of that helps.
I'm creating a product catalog using Flask and Firebase Authentication. I'm following their docs on how to set up both client and server using their pre-built UI and Session Cookies as stated here: https://firebase.google.com/docs/auth/admin/manage-cookies
GET requests work fine, the server verifies the session cookie on each request and sends content accordingly. But when I do a POST request (submitting a form to create a new item, for example) the server is unable to parse the cookie.
I verified using the Chrome Dev Tools that the session cookie sent to the server on both GET and POST requests are the same. Tried several things I've found googling about similar problems but anything worked. I also tried to find a similar question here but I haven't found any.
EDIT: After a few hours looking again at the problem, I've seen that the cookies are NOT the same on the GET and POST request. I've looked at the requests with the Chrome Dev Tools and I've seen that the GET response returns a Set-Cookie header with an invalid cookie (what makes the POST request to have an invalid cookie and redirect again to the login page).
This only happens on pages where login is required (and redirects to login page if you are not logged in), but still I can't find why Flask is sending a Set-Cookie header with an invalid cookie.
EDIT2: After a few more hours, I've found out that removing the CSRF hidden input from the form on those pages fix the cookie problem (the GET request doesn't make a Set-Cookie), so it has to be related with the CSRF thing but I don't know how. There is any special behavior on session cookies when using CSRF that I'm not taking into account?
"New Item" template:
{% extends "layout.html" %}
{% block title %}
New item
{% endblock %}
{% block head %}
{{ super() }}
<link rel="stylesheet" type="text/css" media="screen" href="{{ url_for('static', filename='form.css') }}">
{% endblock %}
{% block content %}
<form action="{{ url_for('newItem') }}" method = 'post'>
<h1>Create a new item</h1>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<label>Name</label>
<input type='text' size='30' name='name' placeholder="Name" required>
<label>Description</label>
<textarea rows='4' name='description' placeholder="Description" required></textarea>
<label>Price</label>
<input type='text' size='30' name='price' placeholder="Price" required>
<label>Image URI</label>
<input type='text' size='30' name='image' placeholder="https://example.com/image.png" required>
<label>Category</label>
<select name='category' required>
{% for c in categories %}
<option value="{{ c.id }}">{{ c.name }}</option>
{% endfor %}
</select>
<input type='submit' value='Create'>
Cancel
</form>
{% endblock %}
"Login Required" decorator:
def login_required(f):
#wraps(f)
def decorated_function(*args, **kwargs):
session_cookie = request.cookies.get('session')
# Verify the session cookie. In this case an additional check is added to detect
# if the user's Firebase session was revoked, user deleted/disabled, etc.
try:
decoded_claims = auth.verify_session_cookie(session_cookie, check_revoked=True)
return f(*args, **kwargs)
except ValueError as e:
# Session cookie is unavailable or invalid. Force user to login.
print(e)
return redirect(url_for('login', mode="select", signInSuccessUrl=request.url))
except auth.AuthError as e:
# Session revoked. Force user to login.
print(e)
return redirect(url_for('login', mode="select", signInSuccessUrl=request.url))
return decorated_function
"Items" endpoint (works as expected):
#app.route('/items/<int:item_id>/')
def item(item_id):
session = DBSession()
item = session.query(Item).get(item_id)
session_cookie = flask.request.cookies.get('session')
# Verify the session cookie. In this case an additional check is added to detect
# if the user's Firebase session was revoked, user deleted/disabled, etc.
try:
auth.verify_session_cookie(session_cookie, check_revoked=True)
return render_template('item.html', item=item, logged=True)
except ValueError as e:
# Session cookie is unavailable or invalid. Force user to login.
print(e)
return render_template('item.html', item=item, logged=False)
except auth.AuthError as e:
# Session revoked. Force user to login.
print(e)
return render_template('item.html', item=item, logged=False)
"New Item" endpoint (returns a Set-Cookie header with an invalid cookie to GET requests):
#app.route('/items/new/', methods=['GET', 'POST'])
#login_required
def newItem():
session_cookie = flask.request.cookies.get('session')
decoded_claims = auth.verify_session_cookie(session_cookie, check_revoked=True)
session = DBSession()
categories = session.query(Category).all()
if request.method == 'GET':
return render_template('new_item.html', categories=categories, logged=True)
else:
# SOME LOGIC HERE
# [...]
return redirect(url_for('item', item_id = newItem.id))
The error I get on the POST request is the following:
Value Error
Can't parse segment: \���
To solve the problem I changed the cookie name from 'session' to any other name. I don't know why Flask keeps sending Set-Cookie headers for the 'session' cookie, so if anyone knows why please comment it out.
I have a flask route that sends a variable as a query string when it is processed (i have a default of None, because the first time this template is rendered no token is needed, or passed) :
#app.route('/results/', methods=['GET', 'POST'], defaults={'token': None})
#app.route('/results/<token>', methods=['GET', 'POST'])
def results(token):
tags = request.form['text'].replace(' ', '').split(',')
data = fetch_data(tags=tags, next_page_token=token)
processed_data, token = process_data(data=data, tags=tags)
return render_template('results.html', data=processed_data[:1], token=token)
this is the template:
<!DOCTYPE html>
<html>
<body style="background-color:powderblue;">
<h1>{{ data }}</h1>
<form action="{{ url_for('results', token=token) }}">
<input type="submit" value="More results plz" />
</form>
</body>
</html>
For some reason when I click the submit button I get a 400 error 10.51.50.1 - - [13/Jul/2017 18:00:45] "GET /results/J0HWWe1OgAAAF0HWWey_wAAAFoAKAA%253D%253D? HTTP/1.1" 400
and this loads on the page
Bad Request
The browser (or proxy) sent a request that this server could not understand.
I think part of the issue is that the ? is after the token i'm passing through, but i'm not sure why. Also when i try to print request.args I don't get anything.
Any thoughts on how to properly send the token back to the route?
You have this line in your view function:
request.form['text']
but I can't find a <input> in your template named text.
I'm writing an enrollment website for my school, and using Django for the framework. For the registration, I require a username, password, and registration token. Those have yet to be validated, all I'm attempting to do right now is go from the registration input page (which uses a POST request) to a "You have successfully registered" page. Somewhere along the line, the csrf token is apparently refusing to be validated.
My view:
def register(request):
return render(request, 'enroller/successfulEnroll.html')
My page:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="{% url 'register' %}" method="post"> {% csrf_token %}
<div class="container">
<label><b>New Username</b></label>
<input type="text" placeholder="Username" name="uname" required>
<br>
<label><b>Password</b></label>
<input type="password" placeholder="Password" name="psw" required>
<br>
<label><b>Registration Password</b></label>
<input type="text" placeholder="Registration Key" name="reg" required>
<br>
<input type="submit" value="Register" />
</div>
</form>
</body>
</html>
When I attempt to go from the registration page to the success page, it gives me an error 403 (CSRF Verification failed. Request aborted). However, when I attempt to go to the url mysite.com/register/, it returns the page I requested with no error.
Is there any way to fix this? I've been looking at RequestContext, but I'm not entirely sure where it would be used.
Got it to work. Daniel was right - it was a problem with the configuration of my middleware. I added two lines before my middleware array in settings.py, and all of the sudden it worked.
SESSION_COOKIE_SECURE = True
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
I can't say I'm entirely sure why it worked, or what the issue was exactly, but it works now. Thanks Daniel!
maybe you can use this method. And djang version is 1.11.1
from django.shortcuts import render
from django.template.context_processors import csrf
form = LoginForm()
c = {'form': form}
c.update(csrf(request))
return render(request, 'a_template.html', c)
I found this method at http://djangobook.com/security-in-django/
For me, work fine, but not the best, because more than a line.