Social Learning Journal - Walking Skeleton
Tags: learningjournal, python, react, firebase
Published: 2021-01-04 02:07:56

---
title: Social Learning Journal - Walking Skeleton
published: true
description: Our next step is to create a walking skeleton, which is an end-to-end test with no stubs against a system that’s deployed in production. We have several unknowns like CORS and authentication to handle in this section.
tags: learningjournal, python, react, firebase
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/v81n5vvqd0w4307nqnwv.jpg
---

Starting off we have a [front-end](https://dev.to/dev3l/social-learning-journal-react-5494) and a [back-end](https://dev.to/dev3l/social-learning-journal-persistence-3og8), but they are not connected. Our next step is to create a [walking skeleton](https://www.henricodolfing.com/2018/04/start-your-project-with-walking-skeleton.html), which is an end-to-end test with no stubs against a system that’s deployed in production. We have several unknowns like [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) and authentication to handle in this section.

# Outcomes

Create an authenticated "Hello, World!" API endpoint that will display text on the Dashboard of the React application.

# API Ping Unauthorized

Let's start by solving a more simple problem. We will handle authentication in the next few steps, but for now, let's just focus on getting a request sent from the front-end to the back-end, and display the results.

In the API project, we have a [/ping](https://dev3l-learning-journal.herokuapp.com/ping) endpoint already. The source for this file is [src/controllers/ping](https://github.com/DEV3L/python-heroku-learning-journal-api/blob/master/src/controllers/ping.py).

```
@ping.route('/', methods=['GET'])
@ping.route('/ping', methods=['GET'])
def hello_world():
    """
    Monitor endpoint
    ---
    tags:
      - ping
    responses:
      200:
        description: Hello, world!
    """
    return 'Hello, world!'
```

# React Hook Into API

My React skills are not as sharp as they used to be. I am using functional components and found that the lifecycle methods of `componentDidMount` and `componentWillMount` were replaced with `useEffect` - some explanation can be found here [add-state-and-lifecycle-methods-to-function-components-with-react-hooks](https://itnext.io/add-state-and-lifecycle-methods-to-function-components-with-react-hooks-8e2bdc44d43d). In the [React Firebase Tutorial](https://www.youtube.com/watch?v=PKwu15ldZ7k) I followed, already utilized this functionality.

I added [axios](https://github.com/axios/axios), a promise-based HTTP client, to the project. This will help us by wrapping the API calls. Then as expected, when trying to call the endpoint I received the following HTTP error:

```
Access to XMLHttpRequest at 'https://dev3l-learning-journal.herokuapp.com/ping' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
```

Switching back to the Python API, we need to add the following code snippet to the app initialization. If we were working in a company production environment, it would not be ideal to allow `*` as our allow origin value. Instead, we would set it to the known front-end domains.

```
@app.after_request
def apply_cors(response):
    response.headers.set("Access-Control-Allow-Origin", "*")
    response.headers.set("Access-Control-Allow-Methods", "GET,POST,OPTIONS")
    response.headers.set("Access-Control-Allow-Headers",
                         "Origin, X-Requested-With, Content-Type, Accept, Authorization")

    return response
```

Once deployed, we can verify that the unauthenticated endpoint returns our `Hello, world!` message successfully.

```
  useEffect(() => {
    async function fetchData() {
      const dataResponse = await axios.get('https://dev3l-learning-journal.herokuapp.com/ping');
      const message = dataResponse.data;

      setMessage(message);
    }
    fetchData();
  }, []);
```

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/mlnzrn4dqdwahkp1cdgm.png)

# Authorization

Next, authentication needs to be added to the targeted endpoint. In order to do this, we will need to add Firebase to the API server. Firebase has a wide array of available features, but we are only using authentication. Given an id token, we should be able to validate the token and get a user from it. [Pyrebase](https://github.com/thisbejim/Pyrebase) is a Python wrapper around Firebase calls. So, we add this to our project and work on getting our authentication hook setup. The Firebase auth object comes with a method to verify an id token:

```
firebase = firebase_instance()
auth = firebase.auth()
account_info = auth.get_account_info(id_token)
```

The client takes a Service Account to use the API from an admin perspective. This was easy to create through the Firebase Console Settings page. The tricky part is that a credential JSON file is used to validate the user on initialization. Since we don't have a true secrets management system, I used a bit of a hacky method to swap out the `private_key` variable in the JSON file with an environment variable.

```
import json
import os

import pyrebase

FIREBASE_API_KEY = os.environ.get("FIREBASE_API_KEY", "api_key")
FIREBASE_AUTH_DOMAIN = os.environ.get("FIREBASE_AUTH_DOMAIN", "auth_domain")
FIREBASE_DATABASE_URL = os.environ.get("FIREBASE_DATABASE_URL", "database_url")
FIREBASE_STORAGE_BUCKET = os.environ.get("FIREBASE_STORAGE_BUCKET", "storage_bucket")
FIREBASE_ADMIN_PRIVATE_KEY = os.environ.get("FIREBASE_ADMIN_PRIVATE_KEY", "admin_private_key")

SERVICE_ACCOUNT_PATH = "./data/firebase-adminsdk.json"
SERVICE_ACCOUNT_PATH_TEMP = "./data/_firebase-adminsdk.json"

config = {
    "apiKey": FIREBASE_API_KEY,
    "authDomain": FIREBASE_AUTH_DOMAIN,
    "databaseURL": FIREBASE_DATABASE_URL,
    "storageBucket": FIREBASE_STORAGE_BUCKET,
    "serviceAccount": SERVICE_ACCOUNT_PATH_TEMP
}

_firebase = None


def firebase_instance():
    global _firebase

    if _firebase:
        return _firebase

    # handle newlines
    firebase_admin_private_key = FIREBASE_ADMIN_PRIVATE_KEY.replace("\\n", "\n")

    with open(SERVICE_ACCOUNT_PATH) as service_account_file:
        service_account_json = json.load(service_account_file)

    service_account_json['private_key'] = firebase_admin_private_key

    with open(SERVICE_ACCOUNT_PATH_TEMP, 'w') as temp_service_account_file:
        json.dump(service_account_json, temp_service_account_file)

    _firebase = pyrebase.initialize_app(config)
    return _firebase

```

With the addition of Firebase we need to add some environment variables to the Heroku application:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/k5qynswn7bsih0mh88p4.png)

These are similar to variables entered into Netlify, with the addition of a `FIREBASE_ADMIN_PRIVATE_KEY` variable. This value is copied from the `adminsdk.json` downloaded from Firebase when the Service Account was created. The file in the project `firebase-adminsdk.json` is checked into GIT but the created filed `_firebase-adminsdk.json` is listed in .gitignore, so it does not get updated and accidentally pushed to the public.

Using `flask_auth`, we create an authentication decorator that can be added to endpoints:

```
from flask_httpauth import HTTPTokenAuth
from requests import HTTPError

from src.dao.firebase import firebase_instance

auth = HTTPTokenAuth(scheme='Bearer')


@auth.verify_token
def verify_token(id_token):
    # firebase singleton
    auth = firebase_instance().auth()

    try:
        auth.get_account_info(id_token)
    except HTTPError:
        return False

    return True
```

At this point in time, we do not care about the user, only that a valid `id_token` was passed to the endpoint. Next, we create a new endpoint `ping_authenticated` that utilizes the `@auth.login_required` decorator.

```
@ping.route('/ping_authenticated', methods=['GET'])
@auth.login_required
def hello_world_authenticated():
    """
    Authenticated monitor endpoint
    ---
    tags:
      - ping_authenticated
    responses:
      200:
        description: Hello, authenticated world!
    """
    return 'Hello, authenticated world!'
```

# Authenticated Requests

Now that we have the API set up, let's switch gears back to the front-end. First, we need to create a service that returns an axios object that has the appropriate header added.

```
// axios_service.js
import axios from 'axios';

export async function axiosRequest(currentUser) {
  const idToken = await currentUser.getIdToken(true);
  axios.defaults.headers.common['Authorization'] = 'Bearer ' + idToken;

  return axios;
}
```

Finally, we tie it all together in the Dashboard.

```
  useEffect(() => {
    async function fetchData() {
      const dataResponse = await axios.get(SERVER_API_URL + '/ping');
      const message = dataResponse.data;
      setMessage(message);

      const request = await axiosRequest(currentUser);
      const authenticatedDataResponse = await request.get(SERVER_API_URL + '/ping_authenticated');
      const authenticatedMessage = authenticatedDataResponse.data;
      setAuthenticatedMessage(authenticatedMessage);
    }
    fetchData();
  }, []);
```

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/7xwhp398bdkqr2utub54.png)

# Conclusion

Although we have not added a lot of valuable functionality to our end users, we have crushed a ton of technical risk and paved the path forward to a more complicated solution. By leveraging Firebase's authentication, we have created an [OAuth](https://en.wikipedia.org/wiki/OAuth) solution within the system - with only a few dozen lines of code on the back and front ends. In addition, using the Walking Skeleton technique, the proposed solution has been validated to work in the production environment. Moving forward in future posts, we will start implementing valuable features that will add muscle and meat to the end product.