Search code examples
reactjsdjangodjango-rest-frameworkenvironment-variablespythonanywhere

How to properly secure API login credentials with environment variables using React and Django


I have a project using React (frontend) and Django Rest Framework (backend), and it is currently deployed on PythonAnywhere. I'm using axios to connect to my API and load data from my database onto the React frontend.

During development, I hardcoded the username and password for accessing the database into my index.js file (credentials are obscured below):

import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App.js';
import axios from 'axios';

import 'semantic-ui-css/semantic.min.css';
import 'lightgallery.js/dist/css/lightgallery.css';
import './styles.css';
import * as serviceWorker from './serviceWorker';

axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
axios.defaults.xsrfCookieName = "csrftoken";
axios.defaults.withCredentials = true;

axios.post('/login/', { username: [HARD_CODED_USERNAME], password: [HARD_CODED_PASSWORD] }).then(rv => {
    console.log('Login', rv)
}).catch(err => {
    console.log('Login error', err.response)
});

const updatePhoto = () => {
    axios.patch('https://[WEBSITEADDRESS.COM]/api/photos/').then(resp => {
        console.log('Update response', resp)
    }).catch(error => {
        console.log("Update error", error)
    })
}

ReactDOM.render(
    <App />,
  document.getElementById('root')
);

serviceWorker.unregister();

This works, however the username and password are viewable upon inspection in the browser. Not only that, but the Django admin as well as the API is accessible to anyone, because it automatically logs them in using my username and password!

I then tried using an .env file located at the root of my create-react-app project (same level with my package.json file):

REACT_APP_USERNAME=myusername
REACT_APP_PASSWORD=mypassword

And I updated my index.js file to as follows:

const my_user_name = process.env.REACT_APP_USERNAME;
const my_password = process.env.REACT_APP_PASSWORD; 

axios.post('/login/', { username: my_user_name, password: my_password }).then(rv => {
    console.log('Login', rv)
}).catch(err => {
    console.log('Login error', err.response)
});

However, this still does not obscure the credentials from inspection in the browser, and, while it does solve the issue of automatically logging anyone into my Django admin and API, the data from the database is not shown on the website.

My questions are as follows:

  1. How do I properly set up axios to access my API and display data on my website WITHOUT logging someone into my Django admin?
  2. How do I properly set up environment variables in React to hide sensitive data when using the browser's inspection tools?

Any help is much appreciated!

UPDATE: SOLUTION

Based on @Lior_Pollak's comment on his answer below, I managed to solve both of my issues by creating a public, read-only API endpoint on the backend. Anyone can view the API but cannot post, update, or delete data, nor can they access the Django admin. And no sensitive data is displayed in the browser's inspection tool, yet all my photos are displayed on the website. :)

In case anyone else stumbles across this question, I've provided the code I've used successfully below (both backend and frontend):

Frontend: index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App.js';

import 'semantic-ui-css/semantic.min.css';
import 'lightgallery.js/dist/css/lightgallery.css';
import './styles.css';
import * as serviceWorker from './serviceWorker';

/*Removed all code related to axios; was not needed here*/

ReactDOM.render(
    <App />,
  document.getElementById('root')
);

serviceWorker.unregister();

Backend: views.py

from django.views.generic import View
from django.http import HttpResponse
from django.conf import settings

from rest_framework import generics
from rest_framework import permissions

from .models import Photo
from .serializers import PhotoSerializer

import logging
import os

class PhotoList(generics.ListCreateAPIView):
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]
    queryset = Photo.objects.filter(show=True).order_by('order')
    serializer_class = PhotoSerializer

class FrontendAppView(View):
    def get(self, request):
        print (os.path.join(settings.REACT_APP_DIR, 'build', 'index.html'))
        try:
            with open(os.path.join(settings.REACT_APP_DIR, 'build', 'index.html')) as f:
                return HttpResponse(f.read())
        except FileNotFoundError:
            logging.exception('Production build of app not found')
            return HttpResponse(status=501)

Backend: urls.py

from django.conf.urls import include, url
from rest_framework import routers
from backend import views

router = routers.DefaultRouter()

urlpatterns = [
    url(r'^api/', include(router.urls)),
    url(r'^api/photos/$', views.PhotoList.as_view()),
]

Solution

  • You are trying to obscure client data, which (for obvious reasons) resides in the client.
    So you can't really obscure it.
    You can force the user to login with their credentials, which is how authentication using username and password is done.