Implementing Authlib with GridDB Part II

Introduction

In a previous blog, we discussed in detail how to implement authlib with GridDB. You can read about that here. That blog introduces you to the concepts of OAuth and explain the basic work flow of using and implementing the authentication library.

For this blog, we will showcase using Authlib inside your actual browser to demonstrate a more real-world use-case. We will also be generating a client id and secret and saving that information directly into your GridDB instance.

The assumption for this blog is that you have GridDB running, along with the python client.

First Steps

To start, we have created a default user of test with a password of the same name. So, first, you can spin up the server with the following command: FLASK_APP=app.py flask run --host=0.0.0.0. Open up your browser and you should be greeted with a log in form. You can simply use the test user and password (user=test, password=test) to log in and get a token. Your browser will save the token; more on this later.

New Features

Client ID And Client Secret Overview

Next, you can click the create client button to be sent to the page made for creating your client id and client secret. An easy/simplified explanation of what the client and client secret are can be found here. Essentially, the client ID and secret allows for a developer using your app/api to secure their app from would-be attackers.

With the OAuth system, every time your application/website tries to have a user log in using their own username and password, the application must also provide the client id that was generated by the OAuth server. This is what they call Two-Legged Authentication and adds an extra layer of security over a simple username/password system.

We discussed the other containers in the previous blog (such as TOKENS), but in this blog, we introduce the CLIENTS container. It is a relatively simple collection container which stores the client id, name, grant types, etc. We will discuss putting rows into this container in a later section.

This is what the container schema looks like:

        conInfo = griddb.ContainerInfo("CLIENTS",
            [["client_id", griddb.Type.STRING],
             ["client_name", griddb.Type.STRING],
             ["client_uri", griddb.Type.STRING],
             ["grant_types", griddb.Type.STRING_ARRAY],
             ["redirect_uri", griddb.Type.STRING_ARRAY],
             ["response_types", griddb.Type.STRING_ARRAY],
             ["scope", griddb.Type.STRING],
             ["token_endpoint_auth_method", griddb.Type.STRING],
             ["client_secret", griddb.Type.STRING]],
            griddb.ContainerType.COLLECTION, True)

For demonstration purposes, the python Flask server provided by this blog is pre-loaded with a default client ID that is already baked in to the code itself. This allows for users to grab valid tokens from the authentication system right away as a sort of proof-of-concept. But of course, if this were a real application, the token would not be generated when attempting to log in until a valid client id was passed in to the server. Instead, the server would respond with an error code.

To create a new client id and secret, you can enter in what URI you would like to use. For grant types, you can enter in password which allows for a simple user/pass for allowing users to generate a token for themselves to properly access your application’s api/data.

Saving And Querying User Credentials

Cookies

In conjunction with the client id & secret functionality, there is now in place a browser cookie which saves a user’s token and status. Cookies are packets of data saved client side (ie. the browser) about the user’s current session. This is the mechanism used to save your preferences and history when you visit your favorite website.

For our implementation, when a user logs in, the token is saved into GridDB and also into as a cookie into the browser. To test in your browser, you can open up your dev tools, navigate over to the “Application” section, and find the Cookies portion. In there, you will find your assigned token from logging in with your client id / secret and username/password combo.

When building real websites with GridDB serving as your backend, I would recommend also using cookies to make the experience much smoother for the users. With cookies, the users can return to their previous state without any hassle on their end. They can also save their credentials and be already logged in unless explicitly telling the application otherwise.

Let’s dig in to how the client id and secret are created and saved into our GridDB server.

Saving And Querying Client ID And Secret

The client id and secret funcionality comes from the create_client() function, coming from the route of the same name in the routes.py file. First, the function will verify that the current user attempting to make a request to the endpoint is logged in and has a valid token. More on the actual querying of the token in the following section.

Next, it checks the type of HTTP request. If it’s a GET request, it will render the create_client.html which is used to create a client id/secret combination via webpage that was previously discussed. If it’s a POST request, this is when a client id and secret are generated and saved based on the user’s information from the GET request page.

The POST request starts with generating a unique code for the client ID; it will also generate the issued_at value. Next, it generates the client_secret (via gen_salt) and saves it into a variable. With the client id and secret generated, we can now put into our GridDB database.

We have already shared what the collection container schema looks like above. So, to put into that container, it looks like this:

    cn.put([client_id, form["client_name"], form["client_uri"], split_by_crlf(form["grant_type"]),
            split_by_crlf(form["redirect_uri"]), split_by_crlf(form["response_type"]), 
            form["scope"], form["token_endpoint_auth_method"], client_secret])

Most of the values are pulled from the user’s direct input on the webpage, but the client id and secret were generated using the gen_salt function from werkzeug.security.

Here is the entire function:

@bp.route('/create_client', methods=('GET', 'POST'))
def create_client():
    user = get_session_token()
    if not user:
        return redirect('/')
    if request.method == 'GET':
        return render_template('create_client.html')

    client_id = gen_salt(24)
    client_id_issued_at = int(time.time())

    
    form = request.form

    if form['token_endpoint_auth_method'] == 'none':
        client_secret = ''
    else:
        client_secret = gen_salt(48)

    conInfo = griddb.ContainerInfo("CLIENTS",
        [["client_id", griddb.Type.STRING],
         ["client_name", griddb.Type.STRING],
         ["client_uri", griddb.Type.STRING],
         ["grant_types", griddb.Type.STRING_ARRAY],
         ["redirect_uri", griddb.Type.STRING_ARRAY],
         ["response_types", griddb.Type.STRING_ARRAY],
         ["scope", griddb.Type.STRING],
         ["token_endpoint_auth_method", griddb.Type.STRING],
         ["client_secret", griddb.Type.STRING]],
        griddb.ContainerType.COLLECTION, True)

    cn = gridstore.put_container(conInfo)

    q = cn.query("select * where client_name = '"+form['client_name']+"' or client_uri = '"+form['client_uri']+"'")
    rs = q.fetch(False)
    if rs.has_next():
        return render_template('create_client.html', error = "Duplicate")

    cn.put([client_id, form["client_name"], form["client_uri"], split_by_crlf(form["grant_type"]),
            split_by_crlf(form["redirect_uri"]), split_by_crlf(form["response_type"]), 
            form["scope"], form["token_endpoint_auth_method"], client_secret])

    return render_template('create_client.html', client_id=client_id, client_secret=client_secret)

Verifying Client ID And Secret

The models.py file also contains a couple of functions which are used to query and verify the veracity of client ids and grant types. The two functions are appriopriately called check_client_secret and check_grant_type.

Grant types can be thought of as methods for which the client application is able to be granted a token. In this blog, we keep it simple and stick to using the password grant type; this just means you simply use username/password combinations to be granted a special token from the server.

The query to verify the grant type being used in the CLIENTS container looks like this:

            cn = gridstore.get_container("CLIENTS")
            q = cn.query("select * where client_id = '"+self.get_client_id()+"'")

It’s the same idea for verifying the client_secret. The query looks like so:

            cn = gridstore.get_container("CLIENTS")
            q = cn.query("select * where client_id = '"+self.get_client_id()+"' and client_secret='"+client_secret+"'")

If the value is found in the database, return True, if not, return False.

Querying User Token

As mentioned before, the user’s token will be saved in the browser, but this does not mean anythng unless this value can be checked and verified on the backend. To do so, the auth server will call a function called get_session_token().

That function looks like this:

def get_session_token():
    print("Session=",session)
    if 'token' in session:
        try:
            cn = gridstore.get_container("TOKENS")
            print("Session: " + session['token'])
            token = session['token']
            que = "select * where token = '"+token+"'"
            q = cn.query(que)
            print(session['token']) 
            q = cn.query("select * where token = '"+session['token']+"'")
            rs = q.fetch(False)
            print(rs.has_next())
            if rs.has_next():
                return session['token']
        except griddb.GSException as e:
            for i in range(e.get_error_stack_size()):
                print("[", i, "]")
                print(e.get_error_code(i))
                print(e.get_location(i))
                print(e.get_message(i))
                print ("ISSUE: ")
                return None
    return None

This function can be called whenever a user tries to access a protected endpoint, essentially forcing a check on a valid token. The first thing this function will do is check that the user has a browser-saved packet of data called “token”. If the user has logged in without error, the session package called token should exist.

Next, a simple query is called to GridDB to verify that the token the user has saved client-side matches with the tokens saved in GridDB. If it’s a match, the function will return with the token data and the rest of the auth-protected services will be granted and will be run normally.

Conclusion

We hope that the showcase of using GridDB with OAuth was beneficial and accurately portrays how easy it can be to share your GridDB instance with the rest of your application.

If you have any questions about the blog, please create a Stack Overflow post here https://stackoverflow.com/questions/ask?tags=griddb .
Make sure that you use the “griddb” tag so our engineers can quickly reply to your questions.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.