Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

flask: how to bridge front-end with back-end service to render api authentication?

Tags:

python

flask

api

In flask-restplus, I want to render API authentication view for my minimal flask API, where whenever when I make a request to the server, the first API should pop up a protective view for asking the user to provide customized token value before using API call. I came up my solution to make API authentication pop view before using api function, but couldn't get that correctly. Can anyone help me out how to make my code work smooth? Any idea?

My current attempt with full implementation:

Here is the partial code of my implementation to do this task.

from functools import wraps
import requests, json, psycopg2, datetime
from time import time
from flask import Flask, request
from flask_sqlalchemy import SQLAlchemy
from flask_restplus import Resource, Api, abort, fields, inputs, reqparse
from itsdangerous import SignatureExpired, JSONWebSignatureSerializer, BadSignature


class AuthenticationToken:
    def __init__(self, secret_key, expires_in):
        self.secret_key = secret_key
        self.expires_in = expires_in
        self.serializer = JSONWebSignatureSerializer(secret_key)

    def generate_token(self, username):
        info = {
            'username': username,
            'creation_time': time()
        }

        token = self.serializer.dumps(info)
        return token.decode()

    def validate_token(self, token):
        info = self.serializer.loads(token.encode())

        if time() - info['creation_time'] > self.expires_in:
            raise SignatureExpired("The Token has been expired; get a new token")

        return info['username']


SECRET_KEY = "f4b58245-6fd4-4bce-a8a4-27ca37370a3c"
expires_in = 600
auth = AuthenticationToken(SECRET_KEY, expires_in)

db = SQLAlchemy(app)

I pretty much coded up all for API authentication but couldn't get the authentication pop view that I expected in my desired output.

Update: output at server endpoint:

When I tried http://127.0.0.1:5000/token at server endpoint, I got Not Found error. How can I get my desired output? any idea?

I am wondering how can I get api protection view that requires a token to access API. currently, I have an error, couldn't get my desired output, so I am hopeful SO community helps me through with this.

desired output:

I want to render a protective view for test API before using API call on the server endpoint. Here is a mockup API authorization view that I want to get:

enter image description here

how can I make this happen using python flask, flask restful? any thought? thanks

like image 839
beyond_inifinity Avatar asked Apr 20 '20 18:04

beyond_inifinity


2 Answers

As the comments suggest, there's no simple snippet of code anyone can share to answer this question. You're basically asking for a five-part blog on how to attach a database to a Flask app in order to authenticate API credentials. I know it doesn't seem this way, but your questions really cascade from one topic Yinto the next. I think your best bet is to look at the Flask Mega Tutorial Part IV Databases and Part V User Logins. These tutorials cover the foundational concepts your code seems to be missing, as follows:

  1. Using SQLalchemy to define your database models
  2. Defining a basic authorization table in your DB
  3. Using encryption so that your authorization tokens can't be lifted from the database
  4. Flushing expired tokens from the auth table
  5. Using pre-built methods to validate authorization such as Flask-Github's github-callback example or Flask-Login's login_required decorator
  6. Using flask-SQLalchemy's create_db yo build the database from your model
  7. Using flask-SQLalchemy's db.session to set/get data from the db

For what it's worth, I really think The Flask Mega-Tutorial would be helpful.

UPDATE: Here is a minimal example using a dictionary as a toy database. A few things about this example ...

  1. If you run main.py and go to http://127.0.0.1:5000/token?username=admin&password=somepassword you will see the working get example

  2. If you go to http://127.0.0.1:5000, click on "hello_world", click "post", and then click "try it out," you can enter a username and a password, and those will be added to the mock database.

  3. After adding a username and password, you can go to http://127.0.0.1:5000/token?username=[]&password=[] except replace brackets with that new username and password. If you shutdown the server, the usernames and passwords won't be saved since it's just updating a dictionary.

Hopefully, all this helps ... once you've edited the app like this, it should be easier to debug issues that aren't related to username and password authentication.

like image 132
Matt L. Avatar answered Oct 22 '22 13:10

Matt L.


I figured what you are doing, and @B--rian, @Matt L. mentioned above, it is not one-shot task that SO community would give help all code base. But this is what I did and I would guide you how you can finish you task with some pains.

we can try to solve this task by dividing several steps, but let's come around this step, I am gonna post nest one shortly:

from functools import wraps
from time import time
from flask import Flask
from flask import request
from flask_restplus import Resource, Api
from flask_restplus import abort
from flask_restplus import fields
from flask_restplus import inputs
from flask_restplus import reqparse
from psycopg2.extensions import AsIs
import psycopg2, datetime, requests, json
from itsdangerous import SignatureExpired, JSONWebSignatureSerializer, BadSignature

'''
## to get API security key, to do:
import uuid
print(str(uuid.uuid4()))
'''

class AuthenticationToken(object):
    def __init__(self, secret_key, expires_in):
        self.secret_key = secret_key
        self.expires_in = expires_in
        self.serializer = JSONWebSignatureSerializer(secret_key)

    def generate_token(self, username):
        info = {
            'username': username,
            'creation_time': time()
        }
        token = self.serializer.dumps(info)
        return token.decode()

    def validate_token(self, token):
        info = self.serializer.loads(token.encode())

        if time() - info['creation_time'] > self.expires_in:
            raise SignatureExpired("The Token has been expired; get a new token")

        return info['username']

SECRET_KEY = "f4b58245-6fd4-4bce-a8a4-27ca37370a3c"
expires_in = 600
auth = AuthenticationToken(SECRET_KEY, expires_in)

app = Flask(__name__)
api = Api(app,authorizations={
                'API-KEY': {
                    'type': 'apiKey',
                    'in': 'header',
                    'name': 'AUTH-TOKEN'
                }
            },
          security='API-KEY',
          default="API AUTH TOKEN", 
          title="immunoMatch RESTful API", 
          description="Immunomatch ED API Authentication View") 

db = psycopg2.connect(database='test_db', user='postgres', password='password', host='localhost', port="5432")

def requires_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):

        token = request.headers.get('AUTH-TOKEN')
        if not token:
            abort(401, 'Authentication token is missing')

        try:
            user = auth.validate_token(token)
        except SignatureExpired as e:
            abort(401, e.message)
        except BadSignature as e:
            abort(401, e.message)

        return f(*args, **kwargs)
    return decorated

credential_model = api.model('credential', {
    'username': fields.String(required=True),
    'password': fields.String(required=True)
})

credential_parser = reqparse.RequestParser()
credential_parser.add_argument('username', type=str)
credential_parser.add_argument('password', type=str)

@api.route('/token')
class Token(Resource):
    @api.expect(credential_parser, validate=True)
    def get(self):
        args = credential_parser.parse_args()
        username = args.get('username')
        password = args.get('password')
        cursor = db.cursor()
        cursor.execute('SELECT * FROM public.authorized_user_table')
        user = cursor.fetchone()

        if username != user[1]:
            api.abort(404, "Username: {} doesn't exist".format(username))
        if password != user[2]:
            api.abort(401, "Wrong password")
        return {"token": auth.generate_token(username)}

if __name__ == '__main__':
    db = psycopg2.connect(database='test_db', user='postgres', password='password', host='localhost', port="5432")
    app.run(debug=True)

I suggest try to digest what @B--rian, @Matt L mentioned in their post, I think @Matt L did point out something worthy you should think of, their advice is nice except not much coding attempt was given. but I would come back to you as soon as I figured out rest.

like image 1
Jerry07 Avatar answered Oct 22 '22 13:10

Jerry07