I have a login function to authenticate the users of my APIs using JWT and have set access and refresh cookies once the user has logged in as per the below code:
login code
#flask_app.route('/login', methods=['POST'])
def login():
email = request.json.get('email')
password = request.json.get('password', None).encode('UTF-8')
#print(email)
#print(password)
if not email:
return('Missing email', 400)
if not password:
return('Missing password', 400)
user = db.session.query(User).filter(User.email == email).first() or None
if user is None:
return('Wrong email or password!'), 400
hashed_password = bcrypt.checkpw(base64.b64encode(sha256(password).digest()), user.hashed_password)
if hashed_password:
access_token = create_access_token(identity=user.id)
refresh_token = create_refresh_token(identity=user.id)
permissions = get_user_permissions(user.id)
resp = jsonify({'email': user.email, 'permissions': permissions})
set_access_cookies(resp, access_token)
set_refresh_cookies(resp, refresh_token)
return resp
When I login I can see the access and refresh cookies in the postman headers section so it is logging in the user correctly.
When I navigate to a route that requires a jwt token I get the error below and cannot see the access token cookie in Postman.
Missing cookie \"access_token_cookie\
jwt_required code snippet
#flask_app.route('/device-data/<unit_id>', methods=['GET'])
#jwt_required
def get_data(unit_id):
find_device = db.session.query(Unit).filter(Unit.id == unit_id).first()
jwt config
jwt = JWTManager(flask_app)
flask_app.config['JWT_CSRF_CHECK_FORM'] = True
flask_app.config['JWT_TOKEN_LOCATION'] = ['cookies']
flask_app.config['JWT_COOKIE_CSRF_PROTECT'] = True
flask_app.config['JWT_COOKIE_SECURE'] = True
flask_app.config['JWT_SECRET_KEY'] = 'Super Secret'
flask_app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(minutes=120)
flask_app.config['JWT_REFRESH_TOKEN_EXPIRES'] = timedelta(days=1)
Related
I have no idea what is going on here.
I have user authentication being performed using flask-login.
In lieu of a password, I am logging users in through Yahoo! social authentication, where their email is being stored and queried to find users in my SQL database.
I followed Miguel Grinberg's mega tutorial for the database implementation and flask-login implementation.
What is weird is that when I run my app locally, I never run into this problem. And also, when I initially push to heroku, everything initially works fine.
The issue is happening seeminly at random, where users are logged out (there is absolutely no reason for this on the flask end), and then once they are logged out, they are unable to log back into flask-login - only AnonymousUser is returned.
What is even more weird, is that I am able to return information about my user object.
Here is my user model class:
from app import db, login
from flask_login import UserMixin
# Follow this link for database management https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-iv-database
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
refresh_token = db.Column(db.String(128))
access_token = db.Column(db.String(128))
access_token_expiry = db.Column(db.DateTime)
email = db.Column(db.String(120), index=True, unique=True)
def __repr__(self):
return '<User {}>'.format(self.email)
#login.user_loader
def load_user(id):
return User.query.get(int(id))
And here is my login route:
#bp.route('/login/callback')
def login_callback():
# Exchange user code for authentication token, which we will store as a "social_id", unique to each user
user_code = request.args.get('code', type=str)
if os.environ.get('FLASK_ENV') == 'production':
client_id = os.environ.get('YAHOO_CLIENT_ID')
client_secret = os.environ.get('YAHOO_CLIENT_SECRET')
redirect_uri = os.environ.get('YAHOO_REDIRECT_URI')
else:
client_id = os.environ.get('DEV_YAHOO_CLIENT_ID')
client_secret = os.environ.get('DEV_YAHOO_CLIENT_SECRET')
redirect_uri = os.environ.get('DEV_YAHOO_REDIRECT_URI')
uri = 'https://api.login.yahoo.com/oauth2/get_token'
data = {
'client_id': client_id,
'client_secret': client_secret,
'redirect_uri': redirect_uri,
'code': user_code,
'grant_type': 'authorization_code'
}
response = requests.post(uri, data)
r_data = response.json()
access_token = r_data['access_token']
refresh_token = r_data['refresh_token']
expires_in = r_data['expires_in']
access_token_expiry = datetime.utcnow() + timedelta(0, expires_in)
# From the response, we get an authentication token, which we can use to create api requests,
# as well as a user unique refresh token, which we will store as a unique user
# To check if user exists in our database, we will request the users email from Yahoo!, which has to be unique
uri = 'https://api.login.yahoo.com/openid/v1/userinfo'
data = {
'Content-type': 'application/json',
'Authorization': 'Bearer ' + access_token
}
response = requests.get(uri, headers=data)
r_data = response.json()
user_email = r_data['email']
# Check email, see if user already exists
user = User.query.filter_by(email=user_email).first()
# If email does not exist, create user
if user is None:
u = User(email=user_email, refresh_token=refresh_token,
access_token=access_token, access_token_expiry=access_token_expiry)
db.session.add(u)
db.session.commit()
user = User.query.filter_by(email=user_email).first()
print('User Created')
else:
# Check if refresh token has changed, if it has, update the refresh token
if user.refresh_token != refresh_token:
u = User.query.filter_by(email=user_email).first()
u.access_token = access_token
u.access_token_expiry = access_token_expiry
u.refresh_token = refresh_token
print('User refresh token refreshed')
db.session.commit()
login_user(user=user, remember=True)
print(user.refresh_token)
print('User Logged In')
return redirect(url_for('main.index'))
The bulk of the login route is the yahoo social authentication, but the login method is called near the end.
Everything worked fine for approximately the past year, and just recently this issue started to occur. I have NO IDEA what is going on, and am pulling my hair out trying to debug this. Please, can anyone give me some sort of indication what could be going wrong here?
I have included a picture showing the error when it happens. Again, nothing changed, just randomly forced to re-login, when this error appears, which doesn't allow me to log in again. You can see that the user's refresh token is printed, thus the user being passed to login_user() is a valid user.
Any information is greatly greatly appreciated.
I am implementing a feature where I can send a reset password email to someone in a Flask website. here's what I have in my init.py file:
app.config['MAIL_SERVER'] = 'smtp.office365.com'
app.config['MAIL_PORT'] = 587
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USERNAME'] = 'email#organisation.org'
app.config['MAIL_PASSWORD'] = 'password'
mail = Mail(app)
Here's what I have in my routes.py file:
def send_rp_email(user):
token = user.get_token()
mess = Message('Password Reset Request', sender="email#organisation.org", recipients=[user.email])
mess.body = f'''This email has been sent since you want to reset your password.
If you did not request to reset your password, please ignore this email.
{url_for('reset_token', token=token, _external=True)}'''
mail.send(mess)
#app.route('/requestpass', methods=['GET', 'POST'])
def reset_request():
check = check_user()
form = RequestResetPass()
if form.validate_on_submit():
email = Users.query.filter_by(email=form.email.data).first()
send_rp_email(email)
flash('An email has been sent to your email address.', 'info')
return redirect(url_for('login'))
return render_template('requestrp.html', form=form)
#app.route('/resetpass/<token>', methods=['GET', 'POST'])
def reset_token(token):
user = Users.verify_token(token)
if user is None:
flash('Invalid token', 'warning')
return redirect(url_for('reset_request'))
form = ResetPass()
if form.validate_on_submit():
hashed_pass = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
user.password = hashed_pass
user = Users.query.filter(Users.id == current_user.id)
user.update({
"password": hashed_pass
})
db.session.commit()
return redirect(url_for('login'))
return render_template('resetpass.html', form=form)
I am getting this error whenever I submit an email address to send the email through:
smtplib.SMTPAuthenticationError: (535, b'5.7.139 Authentication unsuccessful, the request did not meet the criteria to be authenticated successfully. Contact your administrator.
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 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
I'm writing a Pyramid app that allows registration of arbitrary number of users in a database table. This is my login code:
#view_config(route_name='login', renderer='templates/login.jinja2')
def login(request):
username = request.params.get('username', '')
error = ''
if request.method == 'POST':
error = 'Login Failed'
authenticated = False
try:
authenticated = do_login(request)
except ValueError as e:
error = str(e)
if authenticated:
headers = remember(request, username)
return HTTPFound(request.route_url('home'), headers=headers)
return {'error': error, 'username': username}
where
def do_login(request):
username = request.params.get('username', None)
password = request.params.get('password', None)
if not (username and password):
raise ValueError('both username and password required')
manager = BCRYPTPasswordManager()
cur = request.db.cursor()
try:
cur.execute("SELECT password FROM users WHERE username=%s", [username])
except psycopg2.Error:
raise ValueError("That username already exists!")
actual_password = cur.fetchall()[0][0] # Extrrrract the data
return manager.check(actual_password, password)
I want to display the username on all views once a given user is authenticated. My understanding is that the authentication information is stored in a cookie, and that cookie looks like (auth_tkt=""). How do I get the "current" username from this cookie?
Or am I more confused than I realize?
You can get the authenticated username by calling request.authenticated_userid. You can find more about this in official docs.
I also tend to store the whole user object (and dbsession) in the request like this:
def includeme(config):
from .models.user import User
settings = config.get_settings()
dbsession_factory = get_dbsession_factory(settings)
config.add_request_method(
lambda request: dbsession_factory(),
'dbsession',
reify=True)
config.add_request_method(
lambda request: User.get_by_username(
request.dbsession,
request.authenticated_userid),
'user',
reify=True)
def get_dbsession_factory(settings):
engine = engine_from_config(settings, 'sqlalchemy.')
dbsession_factory = sessionmaker()
register(dbsession_factory)
dbsession_factory.configure(bind=engine)
return dbsession_factory
Then you just call config.include('your_app.models') in your app __init__.