vincentdnl

Taking development shortcuts: authentication

April 15th, 2019

    So you have this cool web app idea that should be done quickly. Except it needs user management. That's it. But hey, It won’t be that hard. You have already done that with framework_name. You just have install users_bundle/gem/package to your app, then glue it to your database and at the same time add non domain things to your model. That’s painful but you can still manage to do it. Now you want real users and not throwaway accounts. So you end up adding some kind of phone validation and the time it takes to do all those things skyrockets.

    Being an Indie Maker often means that you are working solo on your project so taking every development shortcut until your product is out there providing value to you is crucial. The last thing your want is taking more than two months before you give your application the opportunity to show itself useful.

    The solution I chose for Astenote was to let the burden of authentication to a third party – in this case Google – by using the OpenID connect flow. By doing this I’ve been able to bootstrap an authentication in a few hours (I’d say something like 6 to 8 hours) which is great because I wanted to get people to use the product and give me feedbacks early on. Astenote is an app where you can store and organize your notes as cards so having the persistent layer working really allows people to note more and more things and come back to it later, seeing the actual value the software provides.

    Using OpenID connect has several advantages like accelerating the sign up process and not having to create multiple passwords for your websites you visite (people end up using the same password over multiple places and once a site end up getting hacked, multiple accounts are compromised).

    Here is how it works:

    OpenID connect flow

    Getting the ID token on the clients side

    The javascript client asks your authorization server for an ID token. It’s a cryptographic token that contains a bunch of informations about the user requiring the token. Most of the providers ask user what information is ok to be used. It’s usually things like “real name”, “email address”, etc. The ID token is signed by the auth provider using a private key and can be verified later on the public key.

    Stateless auth: transmitting the ID token on your API requests

    On your API calls, just pass the token along (in the header ?). When handling the request server side, you just have to check the token on the providers public key. For my backend, I use Python microframework Flask. Google provides a Python (I’m pretty sure there is one for every major language) package called “google-auth” to ease the token checking.

    Here is an example of how it’s done:

    id_info = google.oauth2.id_token.verify_oauth2_token(id_token, google_request, google_client_id)
    

    And then you can check that the iss is correct by doing:

    if id_info['iss'] == 'accounts.google.com':
        # …
    

    And that’s it! At the expense of just a single public key check, you have your authentication up and running!

    Note that Google changes its public key every day so this code:

    session = requests.session()
    cached_session = cachecontrol.CacheControl(session)
    google_request = google.auth.transport.requests.Request(session=cached_session)
    

    will cache the “asking for public key” request so that your backend doesn’t have to do it on every single request it receives.

    What do I do on my backend?

    Now that you receive valid tokens you can handle users and get your business logic separated which is a good thing (separation of concerns anyone?). Here is how I did it with Flask.

    I created a decorator that would:

    • check the token
    • returns an error if it’s not valid (asking the client to get a new one if the old one is expired for example)
    • using the email to get the user in the DB
    • returning the user

    here is a usage example:

    @app.route("/my-route", methods=["POST"])
    @cross_origin()
    @google_auth()
    def my_action(user):
        # business logic using the user object
    

    A consequence of this is that your user item in your database can only just be identified by it’s email for example. As long as the token is valid the email address inside it is considered a valid one. The user model doesn’t have to contain password hash, bearer/refresh token as the authentication is stateless and the token is passed on every request.

    Yeah but I don’t wanna use a third party for authentication...

    You can always decide later to become your own authorization server as a separate service and provide ID tokens that can be used by your service or other services your make, acting like a Single Sign-On (SSO) which makes your user experience a bliss (vs having to retype your password every time you go to a new service).

    There are other solutions, did you try…

    I used bare Google Auth for Astenote. I know there are other solutions out there: Netlify Identity, Auth0, other providers OpenID connect providers like Facebook and Twitter.

    Did you try any of these or another one not listed? How easy was it to set it up with your front-end (React, Angular, no framework…) and your back-end? Please let me know about your solutions to handle users on your projects!

    This article quality is very important to me. Feel free to provide feedbacks or ask for further development on a specific part. You can find me on social networks for any question!