FastAPI AttributeError: 'User' object has no attribute 'password' - python

I have a FastAPI project in which I am using Async SQLAlchemy orm and postgresql as db. Basically it is a simple CRUD based Job Board I've manage to create the CRUD operations successfully and they are working as I expected. The issue I'm stumbled upon is user authentication I'm trying to implementing authentication via JWT on user registration, user will fill out the fields, username, email and password then an verification email will be sent to that user email to verify the JWT token after that is_active field will be True which is by default False. I tried couple of ways but couldn't succeed, I'm having difficulty adding the user to the database.
routes/route_user.py:
from fastapi import APIRouter, HTTPException, status
from fastapi import Depends
from jose import jwt
from db.models.users import User
from schemas.users import UserCreate, ShowUser
from db.repository.users_data_access_layer import Users
from core.auth import Auth
from core.hashing import Hasher
from core.mailer import Mailer
from core.config import Settings
from depends import get_user_db
router = APIRouter()
get_settings = Settings()
#router.post("/", response_model=ShowUser)
async def create_user(form_data: UserCreate = Depends(), users: Users = Depends(get_user_db)):
if await users.check_user(email=form_data.email) is not None:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="User already exists"
)
elif await users.check_username(username=form_data.username) is not None:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Username already exists"
)
new_user = User(email=form_data.email,
username=form_data.username,
hashed_password=Auth.get_password_hash(form_data.password)
)
await users.register_user(new_user)
print(new_user)
confirmation = Auth.get_confirmation_token(new_user.id)
print(confirmation)
new_user.confirmation = confirmation["jti"]
try:
Mailer.send_confirmation_message(confirmation["token"], form_data.email)
except ConnectionRefusedError:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Email couldn't be send. Please try again."
)
return await users.register_user(form_data)
#router.get("/verify/{token}")
async def verify(token: str, users: Users = Depends(get_user_db)):
invalid_token_error = HTTPException(status_code=400, detail="Invalid Token")
try:
payload = jwt.decode(token, get_settings.SECRET_KEY, algorithms=[get_settings.TOKEN_ALGORITHM])
print(payload['sub'])
except jwt.JWSError:
raise HTTPException(status_code=403, detail="Token has Expired")
if payload['scope'] != 'registration':
raise invalid_token_error
print(payload['sub'])
user = await users.get_user_by_id(id=payload['sub'])
print(user)
print('hello2')
if not user or await users.get_confirmation_uuid(str(User.confirmation)) != payload['jti']:
print('hello')
raise invalid_token_error
if user.is_active:
print('hello2')
raise HTTPException(status_code=403, detail="User already Activated")
user.confirmation = None
user.is_active = True
return await users.register_user(user)
the route above outputs the Attribute error:
File ".\db\repository\users_data_access_layer.py", line 26, in register_user
hashed_password=user.password,
AttributeError: 'User' object has no attribute 'password'
user_data_access_layer.py
This is where all db communications are happening. Here I think I need some kind of save method to add to db for convenience but I don't know how to implement it. I tried something like this:
from core.hashing import Hasher
from sqlalchemy.orm import Session
from sqlalchemy.sql.expression import select
from sqlalchemy.sql import exists
from db.models.users import User
from schemas.users import UserCreate
from core.hashing import Hasher
db_session = Session
class Users():
def __init__(self, db_session: Session):
self.db_session = db_session
async def save(self):
if self.id == None:
self.db_session.add(self)
return await self.db_session.flush()
#print('user created')
async def register_user(self, user: UserCreate):
new_user = User(username=user.username,
email=user.email,
hashed_password=user.password,
is_active = False,
is_superuser=False
)
self.db_session.add(new_user)
await self.db_session.flush()
return new_user
async def check_user(self, email: str):
user_exist = await self.db_session.execute(select(User).filter(User.email==email))
#print(user_exist)
return user_exist.scalar_one_or_none()
async def check_username(self, username: str):
user_exist = await self.db_session.execute(select(User).filter(User.username==username))
#print(user_exist)
return user_exist.scalar_one_or_none()
async def get_user_by_id(self, id: int):
user_exist = await self.db_session.execute(select(User).filter(User.id==id)
#print(user_exist)
return user_exist.scalar_one_or_none()
async def get_confirmation_uuid(self, confirmation_uuid:str):
user_exist = await self.db_session.execute(select(User).filter(str(User.confirmation)==confirmation_uuid))
#print(user_exist)
return user_exist
schemas/users.py
from typing import Optional
from pydantic import BaseModel, EmailStr
class UserBase(BaseModel):
username: str
email: EmailStr
password: str
class UserCreate(UserBase):
username: str
email: EmailStr
password: str
class ShowUser(UserBase):
username: str
email: EmailStr
is_active: bool
class Config():
orm_mode = True
models/users.py
import uuid
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy import Column, Integer, String, Boolean, ForeignKey
from sqlalchemy.orm import relationship
from db.base_class import Base
class User(Base):
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
username = Column(String, unique=True, nullable=False)
email = Column(String, nullable=False, unique=True, index=True)
hashed_password = Column(String(255), nullable=False)
is_active = Column(Boolean, default=False)
is_superuser = Column(Boolean, default=False)
confirmation = Column(UUID(as_uuid=True), nullable=True, default=uuid.uuid4)
jobs = relationship("Job", back_populates="owner")
depends.py
from db.session import async_session
from db.repository.jobs_data_access_layer import JobBoard
from db.repository.users_data_access_layer import Users
async def get_job_db():
async with async_session() as session:
async with session.begin():
yield JobBoard(session)
async def get_user_db():
async with async_session() as session:
async with session.begin():
yield Users(session)
Since this is all new stuff and wherever I reached I hit a wall and I'm working on this project for weeks now and couldn't find my way around it yet so any assistance would be appreciate.

There are different problems in the code. Firstly some calls to the method of the class model/Users are called with the wrong parameters. Indeed, some are called with a User object as parameter while those are expecting a Pydantic UserCreate model. So, when you send a User object instead of the Pydantic model, the password attribute does not exist.
Secondly, afterwards, other problems will appear, since your methods to retrieve a User object actually return a list (ChunkIterator). However, you make comparisons as if you were receiving an object.
I took the liberty of proposing an alternative by reformulating some of your code.
Now, I have created methods to save eventual modification of users in your DB, and created some methods that return a user according to different criteria (id, username, email) except that contrary to your code, those return an object (or None) instead of a list.
user_data_access_layer.py
from fastapi import HTTPException, status
from db.models.users import User
from schemas.users import UserCreate
from db_config import SESSION
from auth import Auth
class Users():
def __init__(self):
pass
#classmethod
async def save(cls, user_instance):
try:
SESSION.add(user_instance)
SESSION.commit()
except Exception as error:
SESSION.rollback()
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)
#classmethod
async def get_user_by_id(cls, id):
user = await SESSION.query(User).filter(User.id == id).one_or_none()
return user
#classmethod
async def get_user_by_username(cls, username):
user = await SESSION.query(User).filter(User.username == username).one_or_none()
return user
#classmethod
async def get_user_by_email(cls, email):
user = await SESSION.query(User).filter(User.email == email).one_or_none()
return user
#classmethod
async def get_user_by_confirmation(cls, confirmation):
user = await SESSION.query(User).filter(User.confirmation == confirmation).one_or_none()
return user
#classmethod
async def create_user(self, user: UserCreate):
new_user = User(username=user.username,
email=user.email,
hashed_password=Auth.get_password_hash(user.password)
)
cls.save(new_user)
return new_user
As you can see, I removed the Session creation from your layer file and put it in a global variable, in a separate file.
db_config.py
from sqlalchemy import create_engine
from sqlalchemy.engine import Engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm.session import Session
ENGINE: Engine = create_engine(your_config_url, pool_pre_ping=True)
SESSION: Session = sessionmaker(bind=ENGINE)()
And to conclude, here is your endpoint updated according to the proposed code. I have added comments inside it to make it easier to understand.
route.py
#router.post("/", response_model=ShowUser)
async def create_user(form_data: UserCreate = Depends(), users: Users = Depends(get_user_db)):
# CHECK IF USER ALREADY EXISTS
if await Users.get_user_by_email(email=form_data.email) is not None:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="User already exists"
)
# CHECK IF USERNAME ALREADY EXISTS
elif await Users.get_user_by_username(username=form_data.username) is not None:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Username already exists"
)
# CREATE USER WITH USERS METHOD
# now the hashing of the password is done directly in the creation method
new_user = await Users.create_user(form_data)
# GET TOKEN
# we no longer create a new uid for JTI, but use the one created automatically during user creation
# so I modified the get_confirmation_token function so that it takes the user's JTI uid as a parameter
confirmation_token = Auth.get_confirmation_token(
new_user.id,
new_user.confirmation)

table has not password, table has only hashed_password
new_user = User(username=user.username,
email=user.email,
hashed_password=user.hashed_password,
is_active = False,
is_superuser=False
)

Related

How to redirect to login in FastApi

I am looking to get a simple login sequence on fastapi:
following This tutorial
from fastapi import FastAPI, Depends, HTTPException
from starlette.config import Config
from starlette.requests import Request
from starlette.middleware.sessions import SessionMiddleware
from starlette.responses import HTMLResponse, RedirectResponse
from authlib.integrations.starlette_client import OAuth, OAuthError
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="!secret")
config = Config(".env")
oauth = OAuth(config)
CONF_URL = "https://accounts.google.com/.well-known/openid-configuration"
oauth.register(
name="google",
server_metadata_url=CONF_URL,
client_kwargs={"scope": "openid email profile"},
)
#app.get("/")
async def home(request: Request):
user = request.session.get("user")
if user is not None:
email = user["email"]
html = (
f"<pre>Email: {email}</pre><br>"
'documentation<br>'
'logout'
)
return HTMLResponse(html)
return HTMLResponse('login')
#app.get("/login", tags=["authentication"])
async def login(request: Request):
redirect_uri = request.url_for("auth")
return await oauth.google.authorize_redirect(request, redirect_uri)
#app.get("/auth")
async def auth(request: Request):
try:
token = await oauth.google.authorize_access_token(request)
except OAuthError as error:
return HTMLResponse(f"<h1>{error.error}</h1>")
user = token.get("userinfo")
if user:
request.session["user"] = dict(user)
return RedirectResponse(url="/")
#app.get("/logout", tags=["authentication"])
async def logout(request: Request):
request.session.pop("user", None)
return RedirectResponse(url="/")
# Try to get the logged in user
async def get_user(request: Request) -> Optional[dict]:
user = request.session.get("user")
if user is not None:
return user
else:
raise HTTPException(status_code=403, detail="Could not validate credentials.")
return None
# Endpoint to protect
#app.get("/other_endpoint/")
async def other_endpoint_function(
user: Optional[dict] = Depends(get_user), id: str
):
# Chek if other is authenticated
if user.is_authenticated:
function()
else:
# First redirect to the login page
RedirectResponse("login")
# Once logged in re-run the initial request
RedirectResponse(f"/other_endpoint/{id}")
I am looking to protect only the other_endpoint the homepage has a link to the login page.
check that the user is authenticated before running the function
If the user is authenticated, run function
If the user is not authenticated
redirect to the login page
re-run the function after authentication
So far, I have tried multiple implementations with RedirectResponse, but i ends up bypassing the authentication

Checking for an existing user in a database return user if exists return false if user does not exist

I'm refactoring my app.py for my flask application. I'm trying to get a user.py class handle all user related things. How should I structure my user.py class to return false if that user does not exist in my database?
app.py:
db = get_db_connection()
user = User(db, request.form.get("email"))
if not user.get_email(): # If user does not exist send user to registration page
return redirect("/register", email=request.form.get("email")) # go to the registration page and fill in the used e-mail address
# Check password and send to user_index if correct
password = request.form.get('password')
if user.check_password(password):
session['userID'] = user.get_id()
session['name'] = user.get_first_name()
session['email'] = user.get_email()
return redirect("/user_index")
return message("Wrong Password. Go to log in page to try again.")
user.py:
class User:
def __init__(self, database, email):
db = database
user = db.execute("SELECT * FROM users WHERE users.email = ?", [email]).fetchall()
if user:
self.__id = user['id']
self.__last_name = user['lastName']
self.__first_name = user['firstName']
self.__email = user['email']
self.__date_of_birth = user['dateOfBirth']
self.__hash = user['hash']
I understand that when I instantiate an object in python it should return none. How can I structure my code so that if a user with the given email does not exist I could get some type of false value? Once I get a false value I should be able to redirect the users to the registration page.
If I'm doing this completely wrong please point me in the right direction.
There are many things to mention here:
I recommend using an ORM like SQLAlchemy together with Flask. It will make your life a lot easier and you won't have to worry about some security issues (such as SQL Injection).
If a user is going to log in, it is a good practice to return the message "wrong credentials" if their email OR password is incorrect, so an attacker will not know which one is correct or incorrect, therefore they will have many, many more combinations to try. That is, by just changing the phrase "wrong password" to "wrong credentials" you will be covering a security hole in your application. The same message should be returned if a user is not registered.
I recommend reading up on the uses of underscore in Python, since you are apparently using it in a place where it isn't needed.
In Python you don't need to use getters and setters unless you need to do some preprocessing before getting or setting an attribute.
Finally, in Flask this is a common way to structure a small project:
__init__.py
from flask import Flask
from utils import get_db_connection
app = Flask(__name__)
db = get_db_connection()
utils.py
# Do the necessary imports.
def get_db_connection():
...
run.py
from . import app
if __name__ == "__main__":
app.run() # You can set multiple settings in run().
routes.py
from . import app
from user import User
#app.route("/login", methods=["POST"])
def login():
user = User.get(request.form.get("email"))
# This is not recommended.
# if not user:
# return redirect("/register", email=request.form.get("email"))
password = request.form.get('password')
if user and user.check_password(password):
session['userID'] = user.id
session['name'] = user.first_name
session['email'] = user.email
return redirect("/user_index")
return message("Wrong credentials. Go to log in page to try again.")
models.py
from . import db
class User:
def __init__(self, id, last_name, first_name, email, date_of_birth, hash):
self.id = id
self.last_name = last_name
self.first_name = first_name
self.email = email
self.date_of_birth = date_of_birth
self.hash = hash
def check_password(self, password):
return check_password_hash(self.hash, password)
#staticmethod
def get(email):
user_result = db.execute("SELECT * FROM users WHERE users.email = ?", [email]).fetchall()
if user_result:
return User(user_result['id'], user_result['lastName'], user_result['firstName'],
user_result['email'], user_result['dateOfBirth'], user_result['hash'])
return None
# Declare more classes for your database models!
# class Admin:
#
# ...

Django: ValidationError using is_authenticated and logout when authenticating from an external source

I am currently building a Django application that gets all its data from an external source (via HTTP). I therefore do not want to use my own database. I have already implemented my own user and remote user backend as per the solution stated in this SO question: Django users and authentication from external source. I am using signed cookies for storing sessions.
After implementing the above stated, I was able to login without any errors. However, on checking whether a user is authenticated in a template (or when trying to log him out in a view), I receive an error:
ValidationError at /products/
["'None' value must be an integer."]
at the line in the template
{% if user.is_authenticated %}
where request.user is passed as a template variable user in the corresponding view (I know I shouldn't do that, it's just temporary).
Here is some of the other relevant traceback:
/home/ines/.virtualenvs/diet/lib/python3.6/site-packages/django/contrib/auth/middleware.py in <lambda>
request.user = SimpleLazyObject(lambda: get_user(request))
...
/home/ines/.virtualenvs/diet/lib/python3.6/site-packages/django/contrib/auth/middleware.py in get_user
request._cached_user = auth.get_user(request)
...
/home/ines/.virtualenvs/diet/lib/python3.6/site-packages/django/contrib/auth/__init__.py in get_user
user_id = _get_user_session_key(request)
...
/home/ines/.virtualenvs/diet/lib/python3.6/site-packages/django/contrib/auth/__init__.py in _get_user_session_key
return get_user_model()._meta.pk.to_python(request.session[SESSION_KEY])
The error happens in the to_python() call, where the line 942 in file django/db/models/fields/__init__.py: return int(value) throws an Exception as it receives value='None'.
Let me now post all (hopefully) relevant code from my app.
settings.py
AUTHENTICATION_BACKENDS = ('products.backends.ApiAuthBackend',)
products/backends.py
from django.contrib.auth.backends import RemoteUserBackend
from products.api import API
from products.models import ApiUser
class ApiAuthBackend(RemoteUserBackend):
"""
Authenticate against the API.
"""
create_unknown_user = False # does not create a User object if it is not in the database
def authenticate(self, request, username=None, password=None):
api = API()
access_token = api.login(username=username, password=password)
if api.logged_in():
user = ApiUser()
user.username = username
request.session['access_token'] = access_token
return user
return None
def get_user(self, user_id):
return ApiUser(username=user_id)
products/models.py
from django.contrib.auth.models import AbstractUser
class ApiUser(AbstractUser):
def save(self, **kwargs):
"""
Disables saving to database.
"""
pass
objects = None
username = ''
first_name = ''
last_name = ''
email = ''
password = ''
groups = ''
user_permissions = ''
is_staff = False
is_active = True
is_superuser = False
last_login = None
date_joined = None
def get_group_permissions(self, obj=None):
return []
def get_all_permissions(self, obj=None):
return []
def has_perm(self, perm, obj=None):
return []
def has_perms(self, obj=None, **kwargs):
return []
def has_module_perms(self, app_label):
return []
Upon further inspection of the django/contrib/auth/__init__.py, line 154:
request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
The problem seems to be that the request.session[SESSION_KEY], where SESSION_KEY = '_auth_user_id', is set to 'None'. That's due to the fact that the ApiUser class does not have Fields for attributes anymore - I have overriden them in models.py since I am not using a database. Therefore my instances of ApiUser do not have a pk, and SESSION_KEY attribute is therefore set to 'None'. When Django wants to authenticate, the SESSION_KEY attribute of the session gets passed to the aforementioned to_python() function which in turn raises an Exception.
My question is, how do I fix this? I tried setting the user.pk to always be 1 and setting the session[SESSION_KEY] manually to 1 in the authenticate function in models.py, which has resulted in me not getting an exception but other data written to the session (access_token) has disappeared.
Thank you in advance for your time and answers, and I'm terribly sorry if there happens to be a solution to this somewhere already (I did search extensively but haven't been able to find it).
Update (as per request by Bear Brown):
Here is my code for API class, not sure it's really relevant as it's just used to communicate with the external database but anyway.
products/api.py
import requests
import json
class API:
URL = "[...]"
KEY = "[...]"
LOGIN = "authenticate/access-token"
REGISTER = "authenticate/register"
session = None
debug = True
def __init__(self, **kwargs):
if len(kwargs) > 2 or not kwargs:
return
try:
self.login(**kwargs)
except requests.exceptions.ConnectionError:
print("Could not connect to the database.")
except requests.exceptions.RequestException:
print("Could not retrieve data from the database.")
def login(self, **kwargs):
if self.session is not None:
return
assert 0 < len(kwargs) < 3
access_token = ''
if len(kwargs) == 2:
username, password = kwargs['username'], kwargs['password']
url = self.URL + self.LOGIN
user_data = {
'username': username,
'password': password,
'active-check': 0,
'api_key': self.KEY
}
r = requests.post(url, data=user_data)
r.raise_for_status()
response = r.json()
if response['success'] is True:
access_token = response['access_token']
else: # len(kwargs) == 1
access_token = kwargs['access_token']
if access_token:
self.session = requests.Session()
self.session.headers.update({'Authorization': 'Client-ID ' + access_token})
if self.debug: print("Successfully logged in.")
return access_token
def logout(self):
if self.session is None:
return
self.session.close()
self.session = None
def logged_in(self):
return self.session is not None
Here is a link to the full error traceback on pastebin (it seemed too be too long to add to an already long post):
full error traceback on pastebin.

flask-login user is set to anonymous after login

im new to flask and flask-login and ive been struggling with this for days.
Im trying to log a user in like this:
from creds import auth_username, auth_password, pgsql_dbuser, pgsql_dbpassword, pgsql_db1name
from flask import Flask, render_template, request, Response, redirect, url_for
from flask.ext.bcrypt import Bcrypt
from flask.ext.login import LoginManager, login_required, login_user, current_user, logout_user
import logging
import psycopg2
import uuid
import datetime
app = Flask(__name__)
app.secret_key = str(uuid.uuid4()) # <- required by login_manager.init_app(app)
bcrypt = Bcrypt(app)
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'index'
#app.route('/', methods=['GET','POST'])
def index():
page_name = '/'
if request.method == 'POST':
email = request.form['email']
candidate_password = request.form['password']
user = finduserindbbyemail(email)
if user != None:
password_hash = checkuserpasswordindb(email)
if bcrypt.check_password_hash(password_hash, candidate_password):
user_object = User(user)
result = login_user(user_object) # <- here for successful login
return redirect(url_for('loggedin', user_object=type(user_object), user=user, result=result, current_user=current_user))
else:
user_object = User(user)
error_message = "The password you entered is incorrect"
return render_template('index.html', error_message=error_message)
else:
error_message = "The email address you entered does not match any we have in our records"
return render_template('index.html', error_message=error_message)
if request.method == 'GET':
return render_template('index.html')
I have a User class and a user callback:
class User():
def __init__(self, user):
self.user = user
def is_authenticated(self):
return True
def is_active(self):
return True
def is_anonymous(self):
return False
def get_id(self):
return unicode(self.user)
#login_manager.user_loader
def load_user(user):
con = psycopg2.connect(database=pgsql_db1name, user=pgsql_dbuser, password=pgsql_dbpassword, host='localhost')
uuid = "'"+user+"'"
cur = con.cursor()
cur.execute("SELECT uuid FROM users WHERE uuid = "+ uuid)
uuid = cur.fetchone()
con.close()
if uuid != None:
user = unicode(uuid[0])
return User.get_id(user)
else:
return None
After authentication is successful (apparently?), the user is redirected to a loggedin page which has a #login_required decorator. But instead of loading the loggedin page, the app redirects the user to the login page, telling me the user isnt being logged in?
If try to send values to the page and i remove the #login_required decorator so i can see the page, this is what i see in the browser after 'logging in':
current_user.is_authenticated() = False
current_user.is_active() = False
current_user.is_anonymous() = True
current_user.get_id() = None
user_object = <type 'instance'>
user = 2ca1296c-374d-43b4-bb7b-94b8c8fe7e44
login_user = True
current_user = <flask_login.AnonymousUserMixin object at 0x7f2aec80f190> Logout
It looks like my user hasn't been logged and is being treated as anonymous?
Can anyone see what I've done wrong? I'm having a lot of trouble understanding how this is supposed to work.
Another reason you might not be able to log a user in or current_user is Anonymous after going through your login form: The active=false flag is set on the user in the db. This behavior is confirmed in the docs:
flask_login.login_user(user, remember=False, duration=None, force=False, fresh=True)[source]
Logs a user in. You should pass the actual user object to this. If the user’s is_active property is False, they will not be logged in unless force is True.
This will return True if the log in attempt succeeds, and False if it fails (i.e. because the user is inactive).
So, when you call login_user, you can do this:
login_user(user, remember=form.remember_me.data, force=True), if you want to allow inactive users to log in.
So.. I managed to get it to work, but not using the user_loader callback. For whatever reason, my user loader exhibits the same behaviour as this:
Flask-login with static user always yielding 401- Unauthorized
Anyway, I used a request_loader callback instead based on this example:
http://gouthamanbalaraman.com/blog/minimal-flask-login-example.html
so for a user logging in, which starts here:
if bcrypt.check_password_hash(password_hash, candidate_password):
user_object = User(user, password_hash)
result = login_user(user_object) # <- here for successful login
token = user_object.get_auth_token(user, password_hash)
return redirect(url_for('loggedin', token=token))
I create a user object which has the user's id and their password hash.
then i log the user in. then i create a time-serialized token of the user id and password hash using itsdangerous. the get_auth_token function is part of the User class. it looks like this:
class User():
def __init__(self, user, password_hash):
self.user = user
self.password = password_hash
.
.
.
def get_auth_token(self, user, password):
data = [str(self.user), self.password]
return serializer.dumps(data, salt=serializer_secret)
you need to create a serializer at the beginning of your code somewhere:
serializer = URLSafeTimedSerializer(serializer_secret)
So after the token is created, pass it to the loggedin view as a URL query parameter.
When you try to load a login_required page, like my loggedin page, which is where login_user redirects me to after a successful login, the request_loader callback is executed. it looks like this:
#login_manager.request_loader
def load_user_from_request(request):
if request.args.get('token'):
token = request.args.get('token')
max_age = 1
try:
data = serializer.loads(token, salt=serializer_secret, max_age=max_age)
username = data[0]
password_hash = data[1]
found_user = finduserindbbyuuid(username)
found_password = checkuserpasswordindbbyuuid(username)
if found_user and found_password == password_hash:
user_object = User(found_user, password_hash)
if (user_object.password == password_hash):
return user_object
else:
return None
else:
return None
except BadSignature, e:
pass
else:
return None
This is the point where my user_loader was failing. I was logging in successfully, but the user_loader was always returning None and so my user would be deemed as anonymous.
So with the request loader, it checks that the request URL contains a 'token' argument in the query string. if so, it takes its value and using itsdangerous, deserializes the data.
you can make the token expire with timed serializers, but there are also non timed ones. after the token is deserialized, take the user and password hash and check in the database if they exist, in exactly the same way that the user_loader was supposed to work.. i imagine? my user_loader didnt work so i was most probably doing it wrong.
anyway if a user and password match in the db, then return the user object and bam, login works.
Im not sure if im doing it the right way, cos pretty much flying by the seat of my pants. i saw examples where people used the token_loader, rather than the request_loader callback function, to load the token, but i couldnt figure out how to set & get the auth token to & from the client. maybe ill figure it out... one day...
if you have the same problem, maybe this might help? or just let me know what you think
cheers
I found this page when searching for help with Flask-Login + Flask-Dance. I was seeing current_user as AnonymousUserMixin in a handler with the #login_required decorator. In my case making sure #app.route is on the line above #login_required fixed the problem. The correct order is in the docs: https://flask-login.readthedocs.io/en/latest/#flask_login.login_required.

flask-login: can't understand how it works

I'm trying to understand how Flask-Login works.
I see in their documentation that they use a pre-populated list of users. I want to play with a database-stored users list.
However, I don't understand some things in this Flask-Login module.
#login_manager.user_loader
def load_user(userid):
#print 'this is executed',userid
return user(userid, 'asdf')
This code will be called at every request? This is used to load all the details of my user object?
For now, I have this code:
#app.route('/make-login')
def make_login():
username = 'asdf'
password = 'asdf'
user_data = authenticate(username, password)
user_obj = user(user_data[0], user_data[1])
login_user(user_obj)
return render_template('make-login.html')
When I access /make-login, I want to log in.
My user class:
class user(object):
def __init__(self, id, username, active=True):
self.username = username
self.id = id
#self.active = active
def is_authenticated(self):
return True
def is_active(self):
return True
def is_anonymous(self):
return False
def get_id(self):
return 5
Also, I wrote another two functions for authenticate/register
def authenticate(username, password):
cursor = db.cursor()
password = md5.md5(password).hexdigest()
try:
query = "SELECT * FROM `users` WHERE `username` = %s AND `password` = %s"
cursor.execute(query, (username, password))
results = cursor.fetchall()
#print results[0][0]
#print "here i am"
if not results:
return False
else:
user_data = [results[0][0], results[0][1]]
return user_data
#self.authenticated = True
#self.user_id = results[0][0]
#session['username'] = results['username']
#print type(results)
except db.Error, e:
return 'There was a mysql error'
def register(username, password, email, *args):
cursor = db.cursor()
password = md5.md5(password).hexdigest()
try:
#query = "INSERT INTO `users` (`username`, `password`, `email`) VALUES ('%s', '%s', '%s')" % (username, password, email)
query = "INSERT INTO `users` (`username`, `password`, `email`) VALUES (%s, %s, %s)"
cursor.execute(query, (username, password, email))
db.commit()
return True
except db.Error, e:
print 'An error has been passed. %s' %e
db.rollback()
return False
I don't know how to make this Flask-Login work with MySQL. Also, I don't know if the user are logged-in. How can I get the user ID or the username?
Anyone can explain me in some rows how this Flask-Login works?
Flask-login doesn't actually have a user backend, it just handles the session machinery to help you login and logout users. You have to tell it (by decorating methods), what represents a user and it is also up to you to figure out how to know if a user is "active" or not (since being "active" can mean different things in different applications).
You should read the documentation and be sure what it does and does not do. Here I am only going to concentrate on wiring it up with the db backend.
To start off with, define a user object; which represents properties for your users. This object can then query databases, or LDAP, or whatever and it is the hook that connects the login mechanism with your database backend.
I will be using the login example script for this purpose.
class User(UserMixin):
def __init__(self, name, id, active=True):
self.name = name
self.id = id
self.active = active
def is_active(self):
# Here you should write whatever the code is
# that checks the database if your user is active
return self.active
def is_anonymous(self):
return False
def is_authenticated(self):
return True
Once you have the user object created, you need to write a method that loads the user (basically, creates an instance of the User class from above). This method is called with the user id.
#login_manager.user_loader
def load_user(id):
# 1. Fetch against the database a user by `id`
# 2. Create a new object of `User` class and return it.
u = DBUsers.query.get(id)
return User(u.name,u.id,u.active)
Once you have these steps, your login method does this:
Checks to see if the username and password match (against your database) - you need to write this code yourself.
If authentication was successful you should pass an instance of the user to login_user()
Flask-login will try and load a user BEFORE every request. So yes, your example code below will be called before every request. It is used to check what userid is in the current session and will load the user object for that id.
#login_manager.user_loader
def load_user(userid):
#print 'this is executed',userid
return user(userid, 'asdf')
If you look at the Flask-login source code on github, there is a line under function init_app which goes:
app.before_request(self._load_user)
So before every request, the _load_user function is called. The _load_user functions actually calls another function "reload_user()" based on conditions. And finally, reload_user() function calls your callback function that you wrote (load_user() in your example).
Also, flask-login only provides the mechanism to login/logout a user. It does not care if you are using mysql database.
As per from the Flask-Login's document a user object must be returned and if the user id is not found it should return None instead of Exception.
#login_manager.user_loader
def load_user(userid):
try:
#: Flask Peewee used here to return the user object
return User.get(User.id==userid)
except User.DoesNotExist:
return None
You might want to use Flask-Security, which combines Flask-Login with SQLAlchemy for database access and automates much of the back-end handling of user records.
The Quick Start tutorial will get you started. Set app.config['SQLALCHEMY_DATABASE_URI'] to your MySQL database connection string.
After great everything explained I will try with the code to give a simple example of how to use and at the same time answer it below:
Once you have these steps, your login method does this:
Checks to see if the username and password match (against your database) - you
need to write this code yourself.
If authentication was successful you should pass an instance of the user to
login_user()
Let's say this is the structure of the project:
├─stackoverflow
│ run.py
│
└───site
│ forms.py
│ models.py
│ routes.py
│ site.db
│ __init__.py
│
├───static
│ main.css
│
└───templates
about.html
account.html
home.html
layout.html
login.html
register.html
What interests us most is the model:
# models.py
from site import db, login_manager
from flask_login import UserMixin
#login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
image_file = db.Column(db.String(20), nullable=False, default='default.jpg')
password = db.Column(db.String(60), nullable=False)
def __repr__(self):
return f"User('{self.username}', '{self.email}', '{self.image_file}')"
And we will call it in the user's login, more precisely after the user has registered - after the user exists.
Specifically, the answer to the two steps that need to be implemented can be found in the following two lines of code:
Checks to see if the username and password match (against your database) - you
need to write this code yourself.
A: if user and bcrypt.check_password_hash(user.password, form.password.data):
If authentication was successful you should pass an instance of the user to
login_user()
A: login_user(user, remember=form.remember.data)
# routes.py
from flask import render_template, url_for, flash, redirect, request
from site import app, db, bcrypt
from site.forms import RegistrationForm, LoginForm
from site.models import User
from flask_login import login_user, current_user, logout_user, login_required
#app.route("/")
#app.route("/home")
def home():
return render_template('home.html', title="Welcome")
#app.route("/register", methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('home'))
form = RegistrationForm()
if form.validate_on_submit():
hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
user = User(username=form.username.data, email=form.email.data, password=hashed_password)
db.session.add(user)
db.session.commit()
flash('Your account has been created! You are now able to log in', 'success')
return redirect(url_for('login'))
return render_template('register.html', title='Register', form=form)
#app.route("/login", methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('home'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user and bcrypt.check_password_hash(user.password, form.password.data):
login_user(user, remember=form.remember.data)
next_page = request.args.get('next')
return redirect(next_page) if next_page else redirect(url_for('home'))
else:
flash('Login Unsuccessful. Please check email and password', 'danger')
return render_template('login.html', title='Login', form=form)
# Views that require your users to be logged in can be decorated with the `login_required` decorator
#app.route("/account")
#login_required
def account():
return render_template('account.html', title='Account')
# When the user is ready to log out:
#app.route("/logout")
#login_required
def logout():
logout_user()
return redirect(url_for('home'))
You can then access the logged-in user with the current_user proxy, which is available in every template:
{% if current_user.is_authenticated %}
Hi {{ current_user.username }}!
{% endif %}
By default, when a user attempts to access a login_required view without being logged in, Flask-Login will flash a message and redirect them to the log in view. (If the login view is not set, it will abort with a 401 error.)
The name of the log in view can be set as LoginManager.login_view:
login_manager.login_view = 'login'
# __init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
from flask_login import LoginManager
app = Flask(__name__)
app.config['SECRET_KEY'] = 'ENTER_SECRET_KEY'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
db = SQLAlchemy(app)
bcrypt = Bcrypt(app)
login_manager = LoginManager(app)
login_manager.login_view = 'login'
login_manager.login_message_category = 'info'
from site import routes
And finally, run the project:
# run.py
from site import app
if __name__ == '__main__':
app.run(debug=True)
I hope that, in addition to a great explanation, this simple example is helpful.
Here is a Flask example of using login: https://bitbucket.org/leafstorm/flask-login/src/3160dbfc7cfc/example/login-example.py You need to use #login_required for every method that requires login. For example,
#app.route('/make-login')
#login_required
def make_login():
...
flask-login asks for a User object per request by calling user_loader.
If you use DB each time, you can expect a performance hit. (accepted answer suffers from this)
Your login route, on the other hand, is only called once during the session.
So the typical (session based) implementation should:
Fetch data from DB in your /login route, and cache it in session
Load user from cache in user_loader
Something like this:
#app.route("/login")
def login_callback():
user_data=my_fetch_from_db_based_on_whatever()
if my_check_credentials_ok(user_data)
session["user"]=user_data
login_user(User(user_data))
else:
abort(400)
:
#login_manager.user_loader
def load_user(user_id):
user_data = session["user"]
user=User(user_data)
return user if user.userid==user_id else None

Categories

Resources