Unauthorized access and token not working - python

I have been struggling while trying to get Flask Authentication work .
I'm using this example: https://github.com/miguelgrinberg/REST-auth/blob/master/api.py
I have a user in mysql database, I can generate a token but everytime I make a cURL request with it, I get Access Unauthorized with Exception Bad Signature.
Here is my code:
Users.py:
# coding=utf-8
from sqlalchemy import Column, String, Date, Integer, Numeric, Enum
from common.base import Base
from sqlalchemy import create_engine, ForeignKey
from sqlalchemy.orm import relationship, deferred
from model.DictSerializable import DictSerializable
from passlib.apps import custom_app_context as pwd_context
from itsdangerous import (TimedJSONWebSignatureSerializer
as Serializer, BadSignature, SignatureExpired)
class Users( Base, DictSerializable ):
__tablename__ = 'users'
id = Column(Integer, primary_key = True)
username = Column(String(32), index = True)
password_hash = Column(String(128))
def __init__( self, username, password ):
self.username = username
self.password = password
def hash_password(self, password):
self.password_hash = pwd_context.encrypt(password)
def verify_password(self, password):
return pwd_context.verify(password, self.password_hash)
def generate_auth_token(self, expiration=3600):
s = Serializer('SECRET KEY', expires_in=expiration)
return s.dumps({'id': self.id})
#staticmethod
def verify_auth_token(token):
s = Serializer('SECRET KEY')
try:
data = s.loads(token)
except SignatureExpired:
return None # valid token, but expired
except BadSignature:
print("Bad signature")
return None # invalid token
user = session.query(User).filter(User.id == data['id']).first()
return user
app.py :
auth = HTTPBasicAuth('Bearer')
#auth.error_handler
def unauthorized():
return make_response(jsonify({'error': 'Unauthorized access'}), 403)
#app.route('/v1/api/users', methods = ['POST'])
def new_user():
username = request.json.get('username')
password = request.json.get('password')
if username is None or password is None:
abort(400) # missing arguments
if session.query(Users).filter(Users.username == username).first() is not None:
abort(400) # existing user
user = Users(username, password)
user.hash_password(password)
session.add(user)
session.commit()
return jsonify({ 'username': user.username }), 201, {'Location': url_for('get_user', id = user.id, _external = True)}
#app.route('/v1/api/users/<int:id>')
def get_user(id):
user = session.query(Users.id).all()
if not user:
abort(400)
return jsonify({'username': user.username})
#auth.verify_password
def verify_password(username_or_token, password):
# first try to authenticate by token
user = Users.verify_auth_token(username_or_token)
if not user:
# try to authenticate with username/password
user = session.query(Users).filter_by(username=username_or_token).first()
if not user or not user.verify_password(password):
return False
g.user = user
return True
#app.route('/v1/api/token')
#auth.login_required
def get_auth_token():
token = g.user.generate_auth_token(3600)
return jsonify({'token': token.decode('ascii'), 'duration': 3600})
Here is the using case in the terminal using cURL:
vagrant#vagrant-ubuntu-trusty-64:~/projects$ curl -u souad:souad -i -X GET http://localhost:5000/v1/api/token
HTTP/1.0 401 UNAUTHORIZED
Content-Type: text/html; charset=utf-8
Content-Length: 19
WWW-Authenticate: Bearer realm="Authentication Required"
Server: Werkzeug/0.14.1 Python/2.7.6
Date: Wed, 03 Oct 2018 11:30:04 GMT
Output of the API:
[2018-10-03 10:01:36,253] INFO in _internal: 127.0.0.1 - - [03/Oct/2018 10:01:36] "GET /v1/api/token HTTP/1.1" 401 -
bad signature
[2018-10-03 11:30:04,710] INFO in _internal: 127.0.0.1 - - [03/Oct/2018 11:30:04] "GET /v1/api/token HTTP/1.1" 401 -
I don't know why I get Exception Bad Signature ???
What is the easiest way to authenticate by Flask and protect some endpoints ??

Related

Flask-smorest returning an empty json string

The JSON response of my endpoint returns {} even though I am logging the correct data.
from flask_smorest import Blueprint
bp = Blueprint("auth", __name__, url_prefix="/api/v1/auth/")
#bp.route("/login", methods=["POST"])
#bp.arguments(LoginRequest)
#bp.response(200, JwtTokenResponse)
#bp.response(404, ErrorResponse)
def login(args):
current_app.logger.debug(args)
username = args.get("username", None)
password = args.get("password", None)
current_app.logger.debug(f"Username: {username}")
current_app.logger.debug(f"Password: {password}")
user = User.query.filter_by(username=username).first()
if user is None:
return dict(message="User does not exists"), 404
if not check_password_hash(user.password, password):
return dict(message="Unable to Authenticate user."), 404
access_token = create_access_token(identity=username)
refresh_token = create_refresh_token(identity=username)
response = dict(access_token=access_token, refresh_token=refresh_token)
current_app.logger.debug(f"Response: {response}")
return response, 200
My LoginTokenSchema and ErrorResponse schemas are defined as:
from marshmallow import Schema, fields
class JwtTokenResponse(Schema):
access_token = fields.String()
refresh_token = fields.String()
class ErrorResponse(Schema):
message = fields.String()
When I test the API with a user not in the database or with the wrong password; it will product the correct response with ErrorRespose however with the correct creds it just will output {}, when I check the flask logs I can see access/refresh token dict, what am I doing wrong?
You don't need to call Blueprint.response twice, which is what I did in my question. You can call alt_response with a custom error message or just use abort and let flask-smorest take care of the rest.
#bp.route("/login", methods=["POST"])
#bp.arguments(LoginRequest)
#bp.response(200, SmorestJWTTokenResponse)
def login(args):
current_app.logger.debug(args)
username = args.get("username", None)
password = args.get("password", None)
user = User.query.filter_by(username=username).first()
if user is None:
abort(404, message="User does not exists.")
if not check_password_hash(user.password, password):
abort(404, message="Unable to Authenticate user.")
access_token = create_access_token(identity=username)
refresh_token = create_refresh_token(identity=username)
return dict(access_token=access_token, refresh_token=refresh_token), 200

Restric logged users to access unwanted url routes in a tornado server

I am trying to restrict logged user to access URL routes that are not assigned to them. As soon a user had logged, for example user1, will be redirected to https://myurl.com/user1. So far, that works good, but I would like to avoid that user1 can see the content in the route of user2 in https://myurl.com/user2.
Below you can see the code I am currently using.
import tornado
from tornado.web import RequestHandler
import sqlite3
# could define get_user_async instead
def get_user(request_handler):
return request_handler.get_cookie("user")
# could also define get_login_url function (but must give up LoginHandler)
login_url = "/login"
db_file = "user_login.db"
connection = None
cursor = None
# optional login page for login_url
class LoginHandler(RequestHandler):
def get(self):
try:
errormessage = self.get_argument("error")
except Exception:
errormessage = ""
self.render("login.html", errormessage=errormessage)
def check_permission(self, username, password):
connection = sqlite3.connect(db_file)
cursor = connection.cursor()
cursor.execute("SELECT * FROM users WHERE username=? AND password=?", (username, password))
data = cursor.fetchone()
if username == data[1] and password == data[2]:
return True
return False
def post(self):
username = self.get_argument("username", "")
password = self.get_argument("password", "")
auth = self.check_permission(username, password)
if auth:
self.set_current_user(username)
self.redirect(self.get_argument("next", f"/{username}"))
else:
error_msg = "?error=" + tornado.escape.url_escape(
"Login failed, please try again or contact your system administrator."
)
self.redirect(login_url + error_msg)
def set_current_user(self, user):
if user:
self.set_cookie("user", tornado.escape.json_encode(user))
else:
self.clear_cookie("user")
# optional logout_url, available as curdoc().session_context.logout_url
# logout_url = "/logout"
# optional logout handler for logout_url
class LogoutHandler(RequestHandler):
def get(self):
self.clear_cookie("user")
self.redirect(self.get_argument("next", "/"))

How to do LDAP authentication in flask restful

I need to create LDAP Authentication for a Rest API used within the network of an organization.
I thought I might be able to do this, creating an AuthUtility class :
class AuthUtility:
def __init__(self, user_model, user_data):
self.username = user_model.username
self.password = user_model.password
self.parsed_username = user_data.username
self.parsed_password = user_data.password
self.parsed_auth_method = user_data.auth_method
def _is_ldap_authenticated(self) -> bool:
# TODO: To be replaced by ldap authentication
return False # Hard-Coded
def _is_db_authenticated(self) -> bool:
return self.password == self.parsed_password
def is_authenticated(self) -> bool:
auth_method = self.parsed_auth_method
if auth_method == "ldap":
return self._is_ldap_authenticated()
elif auth_method == "db":
return self._is_db_authenticated()
else:
# This should never happen
print(f"Method {auth_method} not defined!")
# TODO: Return error! This should never happen!
return False
class UserModel(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), nullable=False)
password = db.Column(db.String(80), nullable=False)
#classmethod
def find_all(cls) -> List["UserModel"]:
return cls.query.all()
#classmethod
def find_by_username(cls, username: str) -> "UserModel":
return cls.query.filter_by(username=username).first()
#classmethod
def find_by_id(cls, _id: int) -> "UserModel":
return cls.query.filter_by(id=_id).first()
def save_to_db(self) -> None:
db.session.add(self)
db.session.commit()
def delete_from_db(self) -> None:
db.session.delete(self)
db.session.commit()
And in my post request to the login endpoint, include a key auth_method whose value can be either ldap or db
BODY:
{
"username": "myusername",
"password": "mypassword",
"auth_method": "db"
}
or
BODY:
{
"username": "myusername",
"password": "mypassword",
"auth_method": "ldap"
}
class UserLogin(Resource):
#classmethod
def post(cls):
request_json = request.get_json()
# Pop auth_method from request_json
try:
auth_method = request_json.pop("auth_method")
except KeyError:
return {"message": "Authentication method should be defined"}
user_data = user_schema.load(request_json)
# Find user in the database
user = UserModel.find_by_username(user_data.username)
# Add back auth_method
user_data.auth_method = auth_method
# Check if the user with username exist in the database
if not user:
return {"message": "user_user_not_found"}, 401
# Check to see the user is authenticated
elif not AuthUtility(user, user_data).is_authenticated():
return {"message": "user_invalid_credentials"}, 401
# Create and return access_token and refresh_token
access_token = create_access_token(user.id, fresh=True)
refresh_token = create_refresh_token(user.id)
return {"access_token": access_token, "refresh_token": refresh_token}, 200
The user can log in through either db or ldap auth_methods.
db is clear to me but I do not know how to do it with ldap.
Any idea how to actually complete it and actually using it in production?
I also have found some python ldap3 library but really not sure how to use it
I need to write this part:
def _is_ldap_authenticated(self) -> bool:
# TODO: To be replaced by ldap authentication
return False # Hard-Coded
But the question is, how and is there any resource available I can use and see how to do it?
To add some detail about ldap, please note that I have done this test and it connects:
from ldap3 import SAFE_SYNC, Server, Connection, ALL
ldsp_server = "ldap://***.***.com"
root_dn = "dc=***,dc=com"
ldap_user_name = "***"
ldap_password = "***"
def ldap_test():
server = Server(ldsp_server, get_info=ALL)
conn = Connection(
server,
user=ldap_user_name,
password=ldap_password,
client_strategy=SAFE_SYNC,
auto_bind=True,
)

Handling the token expiration in fastapi

I'm new with fastapi security and I'm trying to implement the authentication thing and then use scopes.
The problem is that I'm setting an expiration time for the token but after the expiration time the user still authenticated and can access services
import json
from jose import jwt,JWTError
from typing import Optional
from datetime import datetime,timedelta
from fastapi.security import OAuth2PasswordBearer,OAuth2PasswordRequestForm,SecurityScopes
from fastapi import APIRouter, UploadFile, File, Depends, HTTPException,status
from tinydb import TinyDB,where
from tinydb import Query
from passlib.hash import bcrypt
from pydantic import BaseModel
from passlib.context import CryptContext
##
class TokenData(BaseModel):
username: Optional[str] = None
class Token(BaseModel):
access_token: str
token_type: str
router = APIRouter()
SECRET_KEY="e79b2a1eaa2b801bc81c49127ca4607749cc2629f73518194f528fc5c8491713"
ALGORITHM="HS256"
ACCESS_TOKEN_EXPIRE_MINUTES=1
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/dev-service/api/v1/openvpn/token")
db=TinyDB('app/Users.json')
Users = db.table('User')
User = Query
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
class User(BaseModel):
username: str
password:str
def get_user(username: str):#still
user= Users.search((where('name') ==name))
if user:
return user[0]
#router.post('/verif')
async def verify_user(name,password):
user = Users.search((where('name') ==name))
print(user)
if not user:
return False
print(user)
passw=user[0]['password']
if not bcrypt.verify(password,passw):
return False
return user
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=1)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
#router.post("/token", response_model=Token)
async def token_generate(form_data:OAuth2PasswordRequestForm=Depends()):
user=await verify_user(form_data.username,form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(data={"sub": form_data.username}, expires_delta=access_token_expires)
return {"access_token": access_token, "token_type": "bearer"}
#router.get('/user/me')
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
token_data = TokenData(username=username)
except JWTError:
raise credentials_exception
user =Users.search(where('name') ==token_data.username)
if user is None:
raise credentials_exception
return user
#router.post('/user')
async def create_user(name,password):
Users.insert({'name':name,'password':bcrypt.hash(password)})
return True
How can I really see the expiration of the token and how can I add the scopes?
I 'd wanted to comment on Unyime Etim's advice
but have no rating yet so this would be a separate answer
I just wanted to add that jwt.decode has a built-in method to check "exp"
and it does check it by default (https://github.com/mpdavis/python-jose/blob/96474ecfb6ad3ce16f41b0814ab5126d58725e2a/jose/jwt.py#L82)
so to make sure your token has been expired you can just handle the corresponding exception ExpiredSignatureError
try:
# decode token and extract username and expires data
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
except ExpiredSignatureError: # <---- this one
raise HTTPException(status_code=403, detail="token has been expired")
except JWTError:
raise credentials_exception
I had pretty much the same confusion when I started out with FastAPI. The access token you created will not expire on its own, so you will need to check if it is expired while validating the token at get_current_user. You could modify your TokenData schema to the code below:
class TokenData(BaseModel):
username: Optional[str] = None
expires: Optional[datetime]
And your get_current_user to:
#router.get('/user/me')
async def get_current_user(token: str = Depends(oauth2_scheme)):
# get the current user from auth token
# define credential exception
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
# decode token and extract username and expires data
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
expires = payload.get("exp")
except JWTError:
raise credentials_exception
# validate username
if username is None:
raise credentials_exception
token_data = TokenData(username=username, expires=expires)
user = Users.search(where('name') == token_data.username)
if user is None:
raise credentials_exception
# check token expiration
if expires is None:
raise credentials_exception
if datetime.utcnow() > token_data.expires:
raise credentials_exception
return user
Scopes is a huge topic on its own, so I can't cover it here. However, you can read more on it at the fastAPI docs here
you don't need to check if it is expired while validating the token at get_current_user(). I was facing same problem. because...
At first I set expiration time==60,
(ACCESS_TOKEN_EXPIRE_MINUTES = 60)
that generated a token, with that token I was testing api.
than I set expiration time==1 and did not generated a token,
(ACCESS_TOKEN_EXPIRE_MINUTES = 1)
expecting that after one minute the previous token will expire but that did not happen because the previous token had 60 minute life.
so, I created new token , tested api with that token. every thing was fine.
after one minute new token got expired.
The answer above does not account that the token_data.expires needs to be converted to a utc date time object.
# check token expiration
if expires is None:
raise credentials_exception
if datetime.utcnow() > datetime.utcfromtimestamp(token_data.expires):
raise credentials_exception
return user
I would modify the example code which doesnt propogate error as JWT does token signature checks and all you need todo is expose the error by propogating the error.
def get_current_user_from_token(
token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)
):
credentials_exception = lambda x : HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=x,
)
try:
payload = jwt.decode(
token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]
)
username: str = payload.get("sub")
print("username/email extracted is ", username)
if username is None:
raise credentials_exception('Could not validate login or error in username/pass')
except JWTError as e:
raise credentials_exception(str(e))
user = get_user(username=username, db=db)
if user is None:
raise credentials_exception('Could not validate login')
return user

get_jwt_identity() returning None in Flask-JWT-Extended

I'm using Flask-JWT-Extended to protect my Flask API. After the Login, in the protected route (add-user), I call get_jwt_identity(), but it's returning None, so I'm not able to get the identity.
#flask_app.route('/<api_version>/login', methods=['POST'])
def login(api_version):
print(f'login', request.form)
response = None
try:
username = request.form['username']
password = request.form['password']
if not username:
return jsonify({"msg": "Missing username parameter"}), 400
if not password:
return jsonify({"msg": "Missing password parameter"}), 400
user = User.get_with_password(username, password)
if (not user):
e1 = Unauthorized(
'Invalid username or password. Please try again.')
e1.status = 401
raise e1
""" flask_login.login_user(user, remember=True) """
access_token = create_access_token(identity=username)
response = json.dumps({"token": access_token}, cls=CustomJSONEncoder)
except Exception as e:
errMsg = f'Error Logging in user {username if username else ""}: {e}'
status = e.status if hasattr(e, 'status') else 500
print(f'{errMsg}')
traceback.print_exc()
return Response(
json.dumps({"message": errMsg, "status": status, "stack": traceback.format_exc() }), status=status, mimetype='application/json')
resp = Response(response, status=200, mimetype='application/json')
return resp
#flask_app.route('/<api_version>/add-user', methods=['POST'])
#jwt_required
def add_user(api_version):
print(f'add-user', request)
response = None
username = None
password = None
allow_admin = None
try:
data = request.get_json()
print(f'add-user data', data)
if 'username' in data:
username = data['username']
else:
return jsonify({"msg": "Missing username parameter"}), 400
if 'password' in data:
password = data['password']
else:
return jsonify({"msg": "Missing password parameter"}), 400
if 'allow_admin' in data:
allow_admin = data['allow_admin']
""" user = User.get_with_password(username, password)"""
user = get_jwt_identity()
print('user',user)
if (not user):
e1 = Unauthorized(
'Invalid username or password. Please try again.')
e1.status = 401
raise e1
response = json.dumps({"user": user}, cls=CustomJSONEncoder)
except Exception as e:
errMsg = f'Error Adding User {username}: {e}'
status = e.status if hasattr(e, 'status') else 500
print(f'{errMsg}')
traceback.print_exc()
return Response(
json.dumps({"message": errMsg, "status": status, "stack": traceback.format_exc() }), status=status, mimetype='application/json')
resp = Response(response, status=200, mimetype='application/json')
return resp
User.py
class User():
#classmethod
def get_with_password(cls, username, password):
print(f'User get_with_password {username} with password')
user_db = account.get_account(username)
print(f'User returned from DB: {user_db}')
user = User()
if not user_db or not len(user_db) or (not 'password' in user_db):
return None
user.username = username
user.id = username
if bcrypt.check_password_hash(user_db['password'], password):
user.role = user_db['role']
#user.is_authenticated = True
print(
f'loginUser returning {vars(user)} ')
return user
return None
I also faced same issue, in my case i forgot to add #jwt_required
#jwt_required
def get(self):
uid = get_jwt_identity()
print("self.uid",uid)
in your code #jwt_required is there, check may be token has expired
I've got the same issue and got it resolved by the following steps
Step1: Check if you have wrapped your app (flask_app) like the following.
from flask_jwt_extended import JWTManager
jwt = JWTManager(flask_app)
Step2: Check if you have JWT_BLACKLIST_ENABLED in your app or not.
If JWT_BLACKLIST_ENABLED = False or JWT_BLACKLIST_ENABLED not present in your app, add the following method above where ever you have used create_token_method() (in your case it is login method).
#jwt.user_claims_loader
def add_claims_to_access_token(identity):
return {
'identity': identity
}
If JWT_BLACKLIST_ENABLED = True then add the following also above your login method.
#jwt.token_in_blacklist_loader
def check_if_token_in_blacklist(decrypted_token):
jti = decrypted_token['jti']
return jti in blacklist

Categories

Resources