Ankur Patil
Ankur Patil

Follow

Ankur Patil

Follow
Access tokens for Specter's REST API: Part 1 | Summer of Bitcoin '22

Access tokens for Specter's REST API: Part 1 | Summer of Bitcoin '22

This blog covers the progress of my Summer of Bitcoin project - Access tokens for the Specter's REST API

Ankur Patil's photo
Ankur Patil
·Jul 3, 2022·

4 min read

Play this article

Table of contents

  • Abstract
  • My Project
  • Project Progress
  • Conclusion

Abstract

Specter Desktop is a desktop GUI for Bitcoin Core optimized to work with hardware wallets. specter.png

Specter already has a REST API system, but the authorization is currently done by HTTPBasicAuth and to improve the security, HTTPTokenAuth is really necessary. Access tokens would be a significant improvement.

My Project

367ei5.jpg

I have to use access tokens for the token-based authorization, the access token I opted for was JSON Web Token (JWT) because:

  • I have already used it
  • I read their official documentation and got a fan of JWT and its advantages over Simple WebToken (SWT) and Security Assertion Markup Language Tokens (SAML). source

JWT authentication flow lzhrko3j6l8uut86tkz1.png

Expected Outcomes:

  • User will be able to create a JWT token when the request is sent to a particular endpoint of the api
  • Once the token is generated the user can only see it once and the token needs to be stored somewhere in order to access sessions later on.
  • Authorization will be based on HTTPTokenAuth

Project Progress

Headstart 🚀

At first, when I went through Specter's codebase it was difficult for me to understand and I was not able to figure out where to make the changes, but thanks to my mentor (k9ert) who helped me sort things out when I was stuck. He suggested making a small FLASK API in a similar structured way as Specter's API was made. This really helped me understand Flask-RESTful and Flask_HTTPAuth.

PoC Implementation 📖

Step 1

The very first step was to install PyJWT:

  pip install PyJWT

Next, we need to create a 'jwt_token' variable in the UserMixinof src/cryptoadvance/specter/user.py, then we pass it in the user_dict with the help of a property.

class User(UserMixin):

     def __init__(
        ...
        jwt_token, 
        ...
    ):
        ...
        self.jwt_token = jwt_token
        ...
    # TODO: User obj instantiation belongs in UserManager
    @classmethod
    def from_json(cls, user_dict, specter):
        try:
            user_args = {
                ...
                // Setting default value of jwt_token to None so that it can be stored in the user's data and then later on updated
                "jwt_token": user_dict.get("jwt_token", None),
                ...
            }
            if not user_dict["is_admin"]:
                user_args["config"] = user_dict["config"]
                return cls(**user_args)
            else:
                user_args["is_admin"] = True
                return cls(**user_args)

        except Exception as e:
            handle_exception(e)
            raise SpecterError(f"Unable to parse user JSON.:{e}")
    @property
    def json(self):
        user_dict = {
            ...
            "jwt_token": self.jwt_token,
            ...
        }
        if not self.is_admin:
            user_dict["config"] = self.config
        return user_dict

We also need to add helper functions in order to fetch and delete the token:

    def save_jwt_token(self, jwt_token):
        self.jwt_token = jwt_token
        self.save_info()

    def delete_jwt_token(
        self,
    ):
        self.jwt_token = None
        self.save_info()

Step 2

This step includes the creation of token based endpoints in the API. For this I created a new file namely jwt.py in the rest directory.

Directory tree Screenshot from 2022-07-03 01-19-24.png

import jwt
from flask import current_app as app
from cryptoadvance.specter.api.rest.base import (
    BaseResource,
    rest_resource,
    AdminResource,
)
import uuid
import datetime
import logging
from ...user import *
from .base import *

from .. import auth

logger = logging.getLogger(__name__)


def generate_jwt(user):
    payload = {
        "user": user.username,
        "exp": datetime.datetime.utcnow() + datetime.timedelta(days=1),
    }
    return jwt.encode(payload, app.config["SECRET_KEY"], algorithm="HS256")


@rest_resource
class TokenResource(AdminResource):
    endpoints = ["/v1alpha/token/"]

    def get(self):
        user = auth.current_user()
        user_details = app.specter.user_manager.get_user(user)
        jwt_token = user_details.jwt_token
        return_dict = {
            "username": user_details.username,
            "id": user_details.id,
            "jwt_token": jwt_token,
        }
        if jwt_token is None:
            return_dict["jwt_token"] = generate_jwt(user_details)
            jwt_token = return_dict["jwt_token"]
            user_details.save_jwt_token(jwt_token)
            return {
                "message": "Token generated",
                "username": user_details.username,
                "jwt_token": jwt_token,
            }
        return {"message": "Token already exists", "jwt_token": jwt_token}

    def delete(self):
        user = auth.current_user()
        user_details = app.specter.user_manager.get_user(user)
        jwt_token = user_details.jwt_token
        if jwt_token is None:
            return {"message": "Token does not exist"}
        user_details.delete_jwt_token()
        return {"message": "Token deleted"}

This basically includes the function generate_jwt which takes user as an argument and generates the token with the payload as user.username and the expiry date of the token (exp).

Then we have two endpoints - GET and DELETE which receive data from the User model using user_manager by passing the authenticated user.

GET request functionality get-req.png

DELETE request functionality delr.png

Last Step

The last step is to register the endpoints in the API, this can be done by adding:

from .jwt import TokenResource

in src/cryptoadvance/specter/api/rest/api.py

PR related to this project github.com/cryptoadvance/specter-desktop/pu..


Demo of the implemented PoC: demo_jwt.gif

Future milestones

The plans for the rest of my journey are:

  • Adding a verfiy_token function that verifies if the given token is correct or not.
  • Replacing HTTPBasicAuth with HTTPTokenAuth
  • Add one-time view functionality for the users.

Conclusion

Thank you for reading, hope you enjoyed it! I'll continue to update my progress via the series of blogs ;)

Follow me on Twitter | LinkedIn for more web development-related tips and posts.

That's all for today! You have read the article till the end.

Did you find this article valuable?

Support Ankur Patil by becoming a sponsor. Any amount is appreciated!

Learn more about Hashnode Sponsors
 
Share this