For a REST API, can I use authentication mechanism provided by flask-login or do I explicitly have to use token based authentication like JWT?

Viewed 5467

Specifications: I am writing a desktop application in Python using PyQt5. My application communicates to the remote database through a Flask API that provides RESTful routes. Flask API and the database are sitting on the same server. On the client-side, I am using the Requests module from Python. I have to implement the user authentication and authorization system in my application. Currently, I am using flask-login.

@app.route("/login", methods=['POST'])
def login():
    user = User.query.filter_by(email = request.form["email"]).first()
    if user and check_password():
       flask_login.login_user(user, remember=request.form['remember'])
       return Response(status=201)
    else:
        return Response(status=401)

In the above code, I am checking if the user exists and hashed password matches. If so, flask_login will automatically login the user and send back a cookie in response which I can use for session management and other stuff.

My question: I have read everywhere that a token-based authentication system is used for securing APIs. But, is there anything wrong with my current approach? Is it not a conventional way to implement authentication? Also, is it less secure in any sense than a token-based system?

1 Answers

The answer to your question is not straight forward, but in short, this is probably a bad way to secure an API in general. However if this is what you're familiar with you could probably get away with doing it this way as their are no 'techincal' reasons why this wouldn't actually work.

API's typically want access credentials/token sent to the API every request. This is similar to how requests are authenticated when the web server (Flask) directly returns the html/js code.

However the difference lies in the mechanic the client uses to submitted proof of authentication. In typical apps where the backend serves the frontend code cookies are used to store session information, and these cookies are sent automatically, by the client (browser) to the backend with every request.

In your case Flask Login is looking at these cookies and verifying their authenticity and determining what user made the request from the info stored in the session on the server.

You mentioned you client is a desktop app using the Python Requests library so if you wanted to continue using the auth method you have you would want to program your desktop client to send requests using the requests.Session object. This basically wraps your requests and stores cookies for you. To do this you make an initial request to login with the request.Session object, and then all subsequent requests would automatically send the cookies and your Flask app would see that you're desktop app is logged in. Check out the docs for more info.

With APIs however, the Flask web server is not going to serve up the frontend code to the client directly. In fact an API shouldn't care if the client even has a frontend. For all the API knows the client could be another script or a user running curl commands in the terminal etc. And importantly the client may or may not have any notion of 'cookies'. So the API needs a way to verify that requests coming in are authorized.

Much like how cookies need to be sent with every request, the API needs something sent with every request that says "Hey server, I'm a request and I'm authorized."

The obvious solution is to send the username and password with every request. This solution is very basic and aptly called Basic Auth. Obviously there are security concerns here, but if the http traffic is encrypted (https) Basic Auth could be ok in the instance where the client is a process running on a secure box that reads the password from a secured file.

Python Requests module has support for this in the: Link to Docs Its as simple as making a request like this:

requests.get('https://api.github.com/user', auth=('user', 'pass'))

then on the Flask backend

@app.route(...)
def some_route():
  username = request.authorization.username
  password = request.authorization.password
  # check to make sure username/password is okay
  # could abstract this code as a decorator and apply it to multiple routes
  # that you want protected by basic auth

There's also the Flask-BasicAuth module that appears to makes it super easy for Flask to accept basic auth, though I've never used it.

However in the instance where the client cannot be assumed to be super secure, say a frontend web app, you as the developer probably don't want to store the username and password directly in browser and chance some one just looking at the local storage etc and seeing the users credentials.

Token based authentication is another, more secure option, Basically the client sends the username/password credentials to the backend once, and exchanges them for a token. This token is then sent with every request the client makes. On the backend, the web server can verify the authenticity of the token and also extract identity from it. The token can be sent through he HTTP header, or through the url query string. IE www.myapi.com/some-end-point?token=12345678

Then if the token is compromised the user could use their same username/password to get another token, and the old token can be expired.

If you want to use the token base auth method check out JSON Web Tokens (JWT) and specifically at the following Flask plugins: Flask-JWT-Extended Flask-Praetorian

Related