flask api with authentication

Flask API with OAuth authentication


Use the JWT (json web token) library with Flask to create a Restful api with OAuth.

It needs to authenticate with username password to get an access token first, and then call the specific API using the token.


On the server side, it creates endpoint /login for requiring a token.

The specific API helloworld uses another endpoint. 

The '@jwt_required()' decorator enables the authentication using token.


from flask import Flask, jsonify, request, Response, make_response

from flask_restful import Api, Resource

from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity


app = Flask(__name__)

api = Api(app)  #restful api

# Setup the Flask-JWT-Extended extension

app.config['JWT_SECRET_KEY'] = 'super-secret'  # Secret key for JWT, Change this in production

jwt = JWTManager(app) 


# Sample users - you would typically store these in a database

users = {

    'user1': 'password1',

    'user2': 'password2'

}


# Authentication endpoint

# if username password is good, return an access token through 'create_access_token'

# needs to jsonify the reponses to be compatible with Restful

class UserLogin(Resource):

    def post(self):

        data = request.get_json()

        username = data.get('username', None)

        password = data.get('password', None)

        if not username or not password or users[username] != password:

            response = make_response(jsonify({"msg": "Invalid credentials"}),400,)

        else:

            access_token = create_access_token(identity=username)

            response = make_response(jsonify({"access_token": access_token}),200,)      

            response.headers["Content-Type"] = "application/json"

            return response


# API endpoint

# jwt_required protects a route. It kicks out requests without a valid JWT token.

class HelloWorld(Resource):

    @jwt_required()

    def get(self):

        current_user = get_jwt_identity()

        response = make_response(jsonify({"message": f"Hello, {current_user}! You're authenticated."}),200,)      

        response.headers["Content-Type"] = "application/json"

        return response

 

# API routes

api.add_resource(UserLogin, '/login')

api.add_resource(HelloWorld, '/hello')


if __name__ == '__main__':

    app.run(debug=True)

On the client side, it needs to log in to get a JWT token first. This is done by sending the username, password to the authentication endpoint.

When a token is returned, use it as Bearer token in the request headers to call the actual API endpoint, i.e. helloworld in this case.


import requests

# Base URL of the Flask application

base_url = 'http://localhost:5000'

# Sample credentials for authentication

username = 'user1'

password = 'password1'

# Login to obtain JWT token

login_url = f'{base_url}/login'

login_data = {'username': username, 'password': password}

response = requests.post(login_url, json=login_data)

if response.status_code == 200:

    access_token = response.json()['access_token'] #get the JWT token

    headers = {'Authorization': f'Bearer {access_token}'} # use the Token in the headers

    # Call the helloworld API endpoint

    hello_url = f'{base_url}/hello'

    response = requests.get(hello_url, headers=headers)

    if response.status_code == 200:

        print(response.json())

    else:

        print('Failed to call helloworld API:', response.status_code)

else:

    print('Failed to authenticate:', response.status_code)

Security / HTTPS / secured API

The previous API is by default in HTTP protocol, so all the requests are in plain text, i.e. very insecure because the username password and tokens can be seen by the public.

To secure the API, it needs to turn on HTTPS. You can enable HTTPS by configuring your server to use SSL/TLS certificates. 

This typically involves obtaining a valid SSL certificate from a trusted Certificate Authority (CA) or a self-signed certificate (for internal use) and configuring your Flask application server (e.g., Gunicorn) to use the certificate for HTTPS connections.


To run a Flask app to use HTTPS, the app.run method needs to include the ssl_context.

if __name__ == '__main__':

    # Enable HTTPS by providing SSL/TLS certificate and key files

    app.run(host='0.0.0.0', port=443, ssl_context=('cert.pem', 'key.pem'))

Replace 'cert.pem' and 'key.pem' with the paths to your SSL/TLS certificate and private key files, respectively.


Generate self-signed certificate (the key and cert pem files)

This can be done using tools like OpenSSL.

Once installed, use below command to create a private RSA key with 2048 bit

   openssl genrsa -out private_key.pem 2048

Then generate a Certificate Signing Request

   openssl req -new -key private_key.pem -out csr.pem

Finally generate the self-signed certificate using the Signing request and private key

   openssl x509 -req -days 365 -in csr.pem -signkey private_key.pem -out certificate.pem

The private_key.pem and certificate.pem can be used to run the Flask app as SSL context.

Note the self-signed certificate is not trusted by default, so clients will receive warnings about it.