Creating Web Sites using Python and Flask

Build a Simple CRUD App with Python, Flask, and React

Build a Simple CRUD App with Python, Flask, and React

Build a Simple CRUD App with Python, Flask, and React: In this tutorial you are going to build a JavaScript application using React in the front-end and we are also going to build a ReST API written in Python which is going to persist. Our app will be a Github open source bookmark project (a.k.a kudo).

In this tutorial you are going to build a JavaScript application using React in the front-end and we are also going to build a ReST API written in Python which is going to persist.

Today’s modern web applications are often built with a server-side language serving data via an API and a front-end javascript framework that presents the data in an easy to use manner to the end user. Python is a dynamic language widely adopted by companies and developers. The language states on its core values that software should simple, readable making developers more productive and happier. You’ll also use Flask to help you to quickly put together a ReST API. React is a declarative, efficient, and flexible JavaScript library developed at Facebook for building user interfaces. It facilitates the creation of complex, interactive, and stateful UIs from small and isolated pieces of code called components.

To complete this tutorial, there are few things you will need:

You will start by creating the back-end.

Create a ReST API with Python

Make sure you have Python 3 installed. Check the version of Python installed by running the following command:

python --version


To install Python 3 you can use pyenv.

If you are using macOS, you can install it using Homebrew:

brew update
brew install pyenv


On a Linux system using the bash shell:

curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash


Once installed, you can run the following commands to install Python 3.

pyenv install 3.6.3
pyenv global 3.6.3


Your ReST API will use some third-party code (libraries) to help you (e.g. to connect to a database, to create schemas for your models, and validate whether the incoming requests are authenticated or not). Python has a powerful tool to manage dependencies called pipenv. To install pipenv on your machine follow these steps:

On macOS:

brew install pipenv

pip install --user pipenv


With pipenv installed, create a directory for your backend code:

mkdir kudos_oss && cd kudos_oss


The command above will create a Python 3 virtual environment. Now you can install Flask by running the following command:

pipenv install flask==1.0.2


Python 3 provides some cool features like absolute_import and print_function that you will use in this tutorial. To import them run the following commands:

touch __init__.py
touch __main__.py


And copy and paste the following content into the main.py file:

from __future__ import absolute_import, print_function


Your backend will need to implement the following user stories:

A normal ReST API will expose endpoints so clients can create, update, delete, read and list all resources. By end of this section your back-end application will be capable to handle the following HTTP calls:

# For the authenticated user, fetches all favorited github open source projects
GET /kudos

# Favorite a github open source project for the authenticated user
POST /kudos

# Unfavorite a favorited github open source project
DELETE /kudos/:id


Define the Python Model Schemas

Your ReST API will have two core schemas, they are GithubRepoSchema and KudoSchema. GithubRepoSchema will represent a Github repository sent by the clients whereas KudoSchema will represent the data you are going to persist in the database.

Go ahead and run the following commands:

mkdir -p app/kudo
touch app/kudo/schema.py
touch app/kudo/service.py
touch app/kudo/__init__.py


The above commands will create the app directory with another directory within it called kudo then, the second command will create three files: schema.py, service.py, and init.py.

Copy and paste the content below within the schema.py file.

from marshmallow import Schema, fields

class GithubRepoSchema(Schema):
  id = fields.Int(required=True)
  repo_name = fields.Str()
  full_name = fields.Str()
  language = fields.Str()
  description = fields.Str()
  repo_url = fields.URL()

class KudoSchema(GithubRepoSchema):
  user_id = fields.Email(required=True)


As you may have noticed, the schemas are inheriting from Schema a package from the [marshmallow library] (https://marshmallow.readthedocs.io/en/3.0/), marshmallow is an ORM/ODM/framework-agnostic library for serializing/deserializing complex data types, such as objects, to and from native Python data types.

Install the marshmallow library running the following commands:

pipenv install marshmallow==2.16.3


Python ReST API Persistence with MongoDB

Great! You have now your first files in place. The schemas were created to represent the incoming request data as well as the data your application persists in the MongoDB. In order to connect and to execute queries against the database, you are going to use a library created and maintained by MongoDB itself called pymongo.

Install the pymongo library running the following commands:

pipenv install pymongo==3.7.2


You can either use MongoDB installed on your machine or you can use docker to spin up a MongoDB container. This tutorial assumes you have Docker and docker-compose installed.

docker-compose will manage the MongoDB container for you.

Create docker-compose.yml

touch docker-compose.yml


Paste the following content into it:

version: '3'
services:
  mongo:
    image: mongo
    restart: always
    ports:
     - "27017:27017"
    environment:
      MONGO_INITDB_ROOT_USERNAME: mongo_user
      MONGO_INITDB_ROOT_PASSWORD: mongo_secret


All you have to do now to spin up a MongoDB container is:

docker-compose up


With MongoDB up and running you are ready to work the MongoRepository class, it is always a good idea to have class with just a single responsibility, so the only point in your back-end application MongoDB is going to be explicitly dealt with is in the MongoRepository.

Start by creating a directory where all persistence related files should sit, a suggestion would be: repository.

mkdir -p app/repository


Then, create the file that will hold the MongoRepository class:

touch app/repository/mongo.py
touch app/repository/__init__.py


With pymongo properly installed and MongoDB up and running, paste the following content into the app/repository/mongo.py file.

import os
from pymongo import MongoClient

COLLECTION_NAME = 'kudos'

class MongoRepository(object):
 def __init__(self):
   mongo_url = os.environ.get('MONGO_URL')
   self.db = MongoClient(mongo_url).kudos

 def find_all(self, selector):
   return self.db.kudos.find(selector)

 def find(self, selector):
   return self.db.kudos.find_one(selector)

 def create(self, kudo):
   return self.db.kudos.insert_one(kudo)

 def update(self, selector, kudo):
   return self.db.kudos.replace_one(selector, kudo).modified_count

 def delete(self, selector):
   return self.db.kudos.delete_one(selector).deleted_count


As you can see the MongoRepository class is straightforward, it creates a database connection on its initialization then saves it to a instance variable to be use later by the methods: find_all, find, create, update, and delete. Notice that all methods explicitly use the pymongo API.

You might have noticed that the MongoRepository class reads a environment variable MONGO_URL . To export the environment variable, run:

export MONGO_URL=mongodb://mongo_user:[email protected]:27017/


Since you might want to use other database in the future, it is a good idea to decouple your application from MongoDB. For the sake of simplicity you are going to create an abstract class to represent a Repository, this class should be the one used throughout your application.

Paste the following content into the app/repository/init.py file:

class Repository(object):
 def __init__(self, adapter=None):
   self.client = adapter()

 def find_all(self, selector):
   return self.client.find_all(selector)

 def find(self, selector):
   return self.client.find(selector)

 def create(self, kudo):
   return self.client.create(kudo)

 def update(self, selector, kudo):
   return self.client.update(selector, kudo)

 def delete(self, selector):
   return self.client.delete(selector)


You might recall the user story that you’re working on is that ann authenticated user should able to create, delete and list all favorited Github open-source projects. In order to get that done those MongoRepository’s methods will come handy.

You will soon implement the endpoints of your ReST API. First, you need to create a service class that knows how to translate the incoming request payload to our representation KudoSchema defined in the app/kudo/schema.py. The difference between the incoming request payload, represented by GithubSchema, and the object you persist in the database, represented by KudoSchema is: The first has an user_Id which determines who owns the object.

Copy the content below to the app/kudo/service.py file:

from ..repository import Repository
from ..repository.mongo import MongoRepository
from .schema import KudoSchema

class Service(object):
 def __init__(self, user_id, repo_client=Repository(adapter=MongoRepository)):
   self.repo_client = repo_client
   self.user_id = user_id

   if not user_id:
     raise Exception("user id not provided")

 def find_all_kudos(self):
   kudos  = self.repo_client.find_all({'user_id': self.user_id})
   return [self.dump(kudo) for kudo in kudos]

 def find_kudo(self, repo_id):
   kudo = self.repo_client.find({'user_id': self.user_id, 'repo_id': repo_id})
   return self.dump(kudo)

 def create_kudo_for(self, githubRepo):
   self.repo_client.create(self.prepare_kudo(githubRepo))
   return self.dump(githubRepo.data)

 def update_kudo_with(self, repo_id, githubRepo):
   records_affected = self.repo_client.update({'user_id': self.user_id, 'repo_id': repo_id}, self.prepare_kudo(githubRepo))
   return records_affected > 0

 def delete_kudo_for(self, repo_id):
   records_affected = self.repo_client.delete({'user_id': self.user_id, 'repo_id': repo_id})
   return records_affected > 0

 def dump(self, data):
   return KudoSchema(exclude=['_id']).dump(data).data

 def prepare_kudo(self, githubRepo):
   data = githubRepo.data
   data['user_id'] = self.user_id
   return data


Notice that your constructor init receives as parameters the user_id and the repo_client which are used in all operations in this service. That’s the beauty of having a class to represent a repository, As far as the service is concerned, it does not care if the repo_client is persisting the data in a MongoDB, PostgreSQL, or sending the data over the network to a third party service API, all it needs to know is the repo_client is a Repository instance that was configured with an adapter that implements methods like create, delete and find_all.

Define Your ReST API Middleware

At this point, you’ve covered 70% of the backend. You are ready to implement the HTTP endpoints and the JWT middleware which will secure your ReST API against unauthenticated requests.

You can start by creating a directory where HTTP related files should be placed.

mkdir -p app/http/api


Within this directory, you will have two files, endpoints.py and middlewares.py. To create them run the following commands:

touch app/http/api/__init__.py
touch app/http/api/endpoints.py
touch app/http/api/middlewares.py


The requests made to your ReST API are JWT-authenticated, which means you need to make sure that every single request carries a valid json web token. pyjwt will take care of the validation for us. To install it run the following command:

pipenv install pyjwt==1.7.1


Now that you understand the role of the JWT middleware, you need to write it. Paste the following content to the middlewares.py file.

from functools import wraps
from flask import request, g, abort
from jwt import decode, exceptions
import json

def login_required(f):
   @wraps(f)
   def wrap(*args, **kwargs):
       authorization = request.headers.get("authorization", None)
       if not authorization:
           return json.dumps({'error': 'no authorization token provied'}), 403, {'Content-type': 'application/json'}

       try:
           token = authorization.split(' ')[1]
           resp = decode(token, None, verify=False, algorithms=['HS256'])
           g.user = resp['sub']
       except exceptions.DecodeError as identifier:
           return json.dumps({'error': 'invalid authorization token'}), 403, {'Content-type': 'application/json'}

       return f(*args, **kwargs)

   return wrap


Flask provide a module called g which is a global context shared across the request life cycle. This middleware is checking whether or not the request is valid, if so, the middleware will extract the authenticated user details and persist them in the global context.

Define Your ReST API Endpoints

The HTTP handlers should be easy now, since you have already done the important pieces, it’s just a matter of putting everything together.

Since your end goal is to create a JavaScript application that will run on web browsers, you need to make sure that web browsers are happy when a preflight is performed, you can learn more about it here. In order to implement CORS our your ReST API, you are going to install flask_cors.

pipenv install flask_cors==3.0.7


Next, implement your endpoints. Go ahead and paste the content above into the app/http/api/endpoints.py file.

from .middlewares import login_required
from flask import Flask, json, g, request
from app.kudo.service import Service as Kudo
from app.kudo.schema import GithubRepoSchema
from flask_cors import CORS

app = Flask(__name__)
CORS(app)

@app.route("/kudos", methods=["GET"])
@login_required
def index():
 return json_response(Kudo(g.user).find_all_kudos())

@app.route("/kudos", methods=["POST"])
@login_required
def create():
   github_repo = GithubRepoSchema().load(json.loads(request.data))

   if github_repo.errors:
     return json_response({'error': github_repo.errors}, 422)

   kudo = Kudo(g.user).create_kudo_for(github_repo)
   return json_response(kudo)

@app.route("/kudo/<int:repo_id>", methods=["GET"])
@login_required
def show(repo_id):
 kudo = Kudo(g.user).find_kudo(repo_id)

 if kudo:
   return json_response(kudo)
 else:
   return json_response({'error': 'kudo not found'}, 404)

@app.route("/kudo/<int:repo_id>", methods=["PUT"])
@login_required
def update(repo_id):
   github_repo = GithubRepoSchema().load(json.loads(request.data))

   if github_repo.errors:
     return json_response({'error': github_repo.errors}, 422)

   kudo_service = Kudo(g.user)
   if kudo_service.update_kudo_with(repo_id, github_repo):
     return json_response(github_repo.data)
   else:
     return json_response({'error': 'kudo not found'}, 404)

@app.route("/kudo/<int:repo_id>", methods=["DELETE"])
@login_required
def delete(repo_id):
 kudo_service = Kudo(g.user)
 if kudo_service.delete_kudo_for(repo_id):
   return json_response({})
 else:
   return json_response({'error': 'kudo not found'}, 404)

def json_response(payload, status=200):
 return (json.dumps(payload), status, {'content-type': 'application/json'})


Brilliant! It’s all in place now! You should be able to run your ReST API with the command below:

FLASK_APP=$PWD/app/http/api/endpoints.py FLASK_ENV=development pipenv run python -m flask run --port 4433


Create the React Client-Side App

To create your React Client-Side App, you will use Facebook’s awesome create-react-app tool to bypass all the webpack hassle.

Installing create-react-app is simple. In this tutorial you will use yarn. Make sure you either have it installed or use the dependency manager of your preference.

To install create-react-app, run the command:

yarn global add create-react-app


You will need a directory to place your React application, go ahead and create the web directory within the pkg/http folder.

mkdir -p app/http/web


Now, create a React application:

cd app/http/web
create-react-app app


create-react-app might take a few minutes to generate the boilerplate application. Go to the recently created app directory and run npm start

By default, the React app generated by create-react-app will run listening on port 3000. Let’s change it to listen to the port 8080.

Change the start command on the file app/http/web/app/package.json to use the correct port.

Then, run the React app.

cd app
npm start


Running npm start will start a web server listening to the port 8080. Open http://localhost:8080/ in your browser. Your browser should load React and render the App.js component created automatically by create-react-app.

Your goal now is to use Material Design to create a simple and beautiful UI. Thankfully, the React community has created https://material-ui.com/ which basically are the Material Design concepts translated to React components.

Run the following commands to install what you will need from Material Design.

yarn add @material-ui/core
yarn add @material-ui/icons


Great, now you have components like: Grid, Card, Icon, AppBar and many more ready to be imported and used. You will use them soon. Let’s talk about protected routes.

Add Authentication to Your React App with Okta

Writing secure user authentication and building login pages are easy to get wrong and can be the downfall of a new project. Okta makes it simple to implement all the user management functionality quickly and securely. Get started by signing up for a free developer account and creating an OpenID Connect application in Okta.

Once logged in, create a new application by clicking Add Application.

Select the Single-Page App platform option.

The default application settings should be the same as those pictured.

Great! With your OIDC application in place, you can now move forward and secure the routes that requires authentication.

Create Your React Routes

React Router is the most used library for routing URLs to React components. React Router has a collection a components that can be used to help the user to navigate in you application.

Your React application will have two routes:

/ The root route does not require the user to be logged in, it actually is the landing page of your application. A user should be able to access this page in order to log in. You will use the Okta React SDK to integrate react-router with Okta’s OpenID Connect API.

/home The Home route will render most of the React components you application will have. It should implement the following user stories.

An authenticated user should be able to search through the Github API the open source projects of his/her preferences An authenticated user should be able to bookmark open source projects that pleases him/her An authenticated user should be able to see in different tabs his/her previously bookmarked open source projects and the search results

To install react-router run the command:

yarn add react-router-dom


And to install the Okta React SDK run the command:

yarn add @okta/okta-react


Now, go head and create your Main component.

mkdir  -p src/Main


Then, within the Main directory create a file named index.js.

touch src/Main/index.js


And paste the following content into the recently created file:

import React, { Component } from 'react';
import { Switch, Route, BrowserRouter as Router } from 'react-router-dom'
import { Security, ImplicitCallback, SecureRoute } from '@okta/okta-react';

import Login from '../Login'
import Home from '../Home'

class Main extends Component {
 render() {
   return (
     <Router>
       <Security
         issuer={yourOktaDomain}
         client_id={yourClientId}
         redirect_uri={'http://localhost:8080/implicit/callback'}
         scope={['openid', 'profile', 'email']}>

         <Switch>
           <Route exact path="/" component={Login} />
           <Route path="/implicit/callback" component={ImplicitCallback} />
           <SecureRoute path="/home" component={Home} />
         </Switch>
       </Security>
     </Router>
   );
 }
}

export default Main;


Don’t worry for now about the Home and Login components. You will work on them soon. Focus on the Security, SecureRoute, and ImplicitCallback components.

For routes to work properly in React, you need to wrap your whole application in a router. Similarly, to allow access to authentication anywhere in the app, you need to wrap the app in a Security component provided by Okta. Okta also needs access to the router, so the Security component should be nested inside the router.

For routes that require authentication, you will define them using the SecureRoute Okta component. If an unauthenticated user tries to access /home, he/she will be redirect to the / root route.

The ImplicitCallback component is the route/URI destination to which the user will be redirected after Okta finishes the sign in process.

Go ahead and change the src/index.js to mount your Main component.

import React from 'react';
import ReactDOM from 'react-dom';
import { Router } from 'react-router-dom'
import { createBrowserHistory } from 'history'

import Main from './Main';

const history = createBrowserHistory();

ReactDOM.render((
  <Router history={history}>
    <Main history={history} />
  </Router>
), document.getElementById('root'))


Your are now ready to create the Login component. As mentioned previously, this component will be accessible to all users (not only authenticated users). The main goal of the Login component is to authenticate the user.

Inside the directory app, you will find a directory called src which stands for source. Go ahead and create a directory named Login.

mkdir  -p src/Login


Then, within the Login directory create a file named index.js.

touch src/Login/index.js


And paste the following content into the file:

import React from 'react'
import Button from '@material-ui/core/Button';
import { Redirect } from 'react-router-dom'
import { withAuth } from '@okta/okta-react';

class Login extends React.Component {
 constructor(props) {
   super(props);
   this.state = { authenticated: null };
   this.checkAuthentication = this.checkAuthentication.bind(this);
   this.login = this.login.bind(this);
 }

 async checkAuthentication() {
   const authenticated = await this.props.auth.isAuthenticated();
   if (authenticated !== this.state.authenticated) {
     this.setState({ authenticated });
   }
 }

 async componentDidMount() {
   this.checkAuthentication()
 }

 async login(e) {
   this.props.auth.login('/home');
 }

 render() {
   if (this.state.authenticated) {
     return <Redirect to='/home' />
   } else {
     return (
       <div style={{height: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center'}}>
         <Button variant="contained" color="primary" onClick={this.login}>Login with Okta</Button>
       </div>
     )
   }
 }
}

export default withAuth(Login);


In order to see the Login page working, you need to create a placeholder for the Home component.

Go ahead and create a directory called Home.

mkdir -p src/Home


Then, within that directory, create a file named index.js.

touch src/Home/index.js


And paste the following content into it:

import React from 'react'

const home = (props) => {
  return (
    <div>Home</div>
  )
};

export default home;


Now try running npm start and open http://localhost:8080 in your browser. You should see the page below.

In the Login component you are using the Okta React SDK to check whether the user has signed in… If the user has already signed in, they should be redirected to the /home route, otherwise he/she could click Login With Okta to be redirected to Okta, authenticate and be sent to the the home page.

For now, the home page is blank, but eventually here’s what you’ll want the home page to look like:

The Home component is composed of Material Design components like: Tab, AppBar, Button, and Icon as well as a few custom components you will have to create.

For your app, you need to list all the bookmarked open source projects as well as the search results. As you can see in the image above, the Home component is using a tabs to separate bookmarked open source projects from search results, the first tab is listing all the open source projects bookmarked by the user whereas the second tab will list the search results.

You can create a component to represent an open source project in both “Kudos” and “Search Results” lists, that’s the beauty of React components they are highly flexible and reusable.

Go ahead and create a directory called GithubRepo

mkdir -p src/GithubRepo


Then, within that directory, create a file named index.js

touch src/GithubRepo/index.js


And paste the following content into it:

import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import Card from '@material-ui/core/Card';
import CardHeader from '@material-ui/core/CardHeader';
import CardContent from '@material-ui/core/CardContent';
import CardActions from '@material-ui/core/CardActions';
import IconButton from '@material-ui/core/IconButton';
import Typography from '@material-ui/core/Typography';
import FavoriteIcon from '@material-ui/icons/Favorite';

const styles = theme => ({
  card: {
    maxWidth: 400,
  },
  media: {
    height: 0,
    paddingTop: '56.25%', // 16:9
  },
  actions: {
    display: 'flex',
  }
});

class GithubRepo extends React.Component {
  handleClick = (event) =>  {
    this.props.onKudo(this.props.repo)
  }

  render() {
    const { classes } = this.props;

    return (
      <Card className={classes.card}>
        <CardHeader
          title={this.props.repo.full_name}
        />
        <CardContent>
          <Typography component="p" style={{minHeight: '90px', overflow: 'scroll'}}>
            {this.props.repo.description}
          </Typography>
        </CardContent>
        <CardActions className={classes.actions} disableActionSpacing>
          <IconButton aria-label="Add to favorites" onClick={this.handleClick}>
            <FavoriteIcon color={this.props.isKudo ? "secondary" : "primary"} />
          </IconButton>
        </CardActions>
      </Card>
    );
  }
}

export default withStyles(styles)(GithubRepo);


The GithubRepo is a quite simple component, it receives two props: A repo object which holds a reference to a Github repository and an isKudo boolean flag that indicates whether the repo has been bookmarked or not.

The next component you will need is the SearchBar. It will have two responsibilities: log the user out and call React on every press of the Enter key in the search text field.

Create a directory called SearchBar

mkdir -p src/SearchBar


Then, within the directory, create a file named index.js

touch src/SearchBar/index.js


Paste the following content:

import React from 'react';
import PropTypes from 'prop-types';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import InputBase from '@material-ui/core/InputBase';
import Button from '@material-ui/core/Button';
import { fade } from '@material-ui/core/styles/colorManipulator';
import { withStyles } from '@material-ui/core/styles';
import SearchIcon from '@material-ui/icons/Search';
import { withAuth } from '@okta/okta-react';

const styles = theme => ({
  root: {
    width: '100%',
  },
  MuiAppBar: {
    alignItems: 'center'
  },
  grow: {
    flexGrow: 1,
  },
  title: {
    display: 'none',
    [theme.breakpoints.up('sm')]: {
      display: 'block',
    },
  },
  search: {
    position: 'relative',
    borderRadius: theme.shape.borderRadius,
    backgroundColor: fade(theme.palette.common.white, 0.15),
    '&:hover': {
      backgroundColor: fade(theme.palette.common.white, 0.25),
    },
    marginRight: theme.spacing.unit * 2,
    marginLeft: 0,
    width: '100%',
    [theme.breakpoints.up('sm')]: {
      marginLeft: theme.spacing.unit * 3,
      width: 'auto',
    },
  },
  searchIcon: {
    width: theme.spacing.unit * 9,
    height: '100%',
    position: 'absolute',
    pointerEvents: 'none',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
  },
  inputRoot: {
    color: 'inherit',
    width: '100%',
  },
  inputInput: {
    paddingTop: theme.spacing.unit,
    paddingRight: theme.spacing.unit,
    paddingBottom: theme.spacing.unit,
    paddingLeft: theme.spacing.unit * 10,
    transition: theme.transitions.create('width'),
    width: '100%',
    [theme.breakpoints.up('md')]: {
      width: 400,
    },
  },
  toolbar: {
    alignItems: 'center'
  }
});

class SearchBar extends React.Component {
  constructor(props) {
    super(props);
    this.logout = this.logout.bind(this);
  }

  async logout(e) {
    e.preventDefault();
    this.props.auth.logout('/');
  }

  render() {
    const { classes } = this.props;

    return (
      <div className={classes.root}>
        <AppBar position="static" style={{alignItems: 'center'}}>
          <Toolbar>
            <div className={classes.search}>
              <div className={classes.searchIcon}>
                <SearchIcon />
              </div>
              <InputBase
                placeholder="Search for your OOS project on Github + Press Enter"
                onKeyPress={this.props.onSearch}
                classes={{
                  root: classes.inputRoot,
                  input: classes.inputInput,
                }}
              />
            </div>
            <div className={classes.grow} />
            <Button onClick={this.logout} color="inherit">Logout</Button>
          </Toolbar>
        </AppBar>
      </div>
    );
  }
}

SearchBar.propTypes = {
  classes: PropTypes.object.isRequired,
};

export default withStyles(styles)(withAuth(SearchBar));


The SearchBar component receives one prop called onSearch which is the function that should be called in each keyPress event triggered in the search text input.

The SearchBar uses the withAuth helper provided by Okta React SDK which will inject the auth object in the props of the component. The auth object has a method called logout that will wipe out all user related data from the session. This is exactly what you want in order to log the user out.

Now it’s time to work on the Home component. One of the dependencies the component has is the react-swipeable-views library which will add nice animations when the user changes tabs.

To install react-swipeable-views, run the command:

yarn add react-swipeable-views


You will also need to make HTTP calls to your Python ReST API as well as to the Github ReST API. The Github HTTP client will need to have a method or function to make a request to this URL: https://api.github.com/search/repositories?q=USER-QUERY. You are going to use the q query string to pass the term the user wants to query against Github’s repositories.

Create a file named githubClient.js.

touch src/githubClient.js


Paste the following content in it:

export default {
 getJSONRepos(query) {
   return fetch('https://api.github.com/search/repositories?q=' + query).then(response => response.json());
 }
}


Now, you need to create an HTTP client to make HTTP calls to the Python ReST API you implemented in the first section of this tutorial. Since all the requests made to your Python ReST API require the user to be authenticated, you will need to set the Authorization HTTP Header with the accessToken provided by Okta.

Go ahead and create a file named apiClient.js

touch src/apiClient.js


And install axios to help you to perform HTTP calls to your flask API.

yarn add axios


Then, paste the following content:

import axios from 'axios';

const BASE_URI = 'http://localhost:4433';

const client = axios.create({
 baseURL: BASE_URI,
 json: true
});

class APIClient {
 constructor(accessToken) {
   this.accessToken = accessToken;
 }

 createKudo(repo) {
   return this.perform('post', '/kudos', repo);
 }

 deleteKudo(repo) {
   return this.perform('delete', `/kudos/${repo.id}`);
 }

 getKudos() {
   return this.perform('get', '/kudos');
 }

 async perform (method, resource, data) {
   return client({
     method,
     url: resource,
     data,
     headers: {
       Authorization: `Bearer ${this.accessToken}`
     }
   }).then(resp => {
     return resp.data ? resp.data : [];
   })
 }
}

export default APIClient;


Great! Your APIClient’s method perform is adding the user’s accessToken to the Authorization HTTP header of every request, which means, it’s authenticating every request. When the server receives these HTTP requests your Okta middleware will be able to verify the token and to extract user details from it as well.

Normally, you might create separate components for getting the user’s bookmarks and for searching for github repos. For simplicity’s sake you’ll put them all in the HomeComponent

Paste the following content in the src/Home/index.js file.

import React from 'react';
import { withStyles } from '@material-ui/core/styles';
import SwipeableViews from 'react-swipeable-views';
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';
import Grid from '@material-ui/core/Grid';
import { withAuth } from '@okta/okta-react';

import GithubRepo from "../GithubRepo"
import SearchBar from "../SearchBar"

import githubClient from '../githubClient'
import APIClient from '../apiClient'

const styles = theme => ({
 root: {
   flexGrow: 1,
   marginTop: 30
 },
 paper: {
   padding: theme.spacing.unit * 2,
   textAlign: 'center',
   color: theme.palette.text.secondary,
 },
});

class Home extends React.Component {
 state = {
   value: 0,
   repos: [],
   kudos: []
 };

 async componentDidMount() {
   const accessToken = await this.props.auth.getAccessToken()
   this.apiClient = new APIClient(accessToken);
   this.apiClient.getKudos().then((data) =>
     this.setState({...this.state, kudos: data})
   );
 }

 handleTabChange = (event, value) => {
   this.setState({ value });
 };

 handleTabChangeIndex = index => {
   this.setState({ value: index });
 };

 resetRepos = repos => this.setState({ ...this.state, repos })

 isKudo = repo => this.state.kudos.find(r => r.id == repo.id)
  onKudo = (repo) => {
   this.updateBackend(repo);
 }

 updateBackend = (repo) => {
   if (this.isKudo(repo)) {
     this.apiClient.deleteKudo(repo);
   } else {
     this.apiClient.createKudo(repo);
   }
   this.updateState(repo);
 }

 updateState = (repo) => {
   if (this.isKudo(repo)) {
     this.setState({
       ...this.state,
       kudos: this.state.kudos.filter( r => r.id !== repo.id )
     })
   } else {
     this.setState({
       ...this.state,
       kudos: [repo, ...this.state.kudos]
     })
   }
 }

 onSearch = (event) => {
   const target = event.target;
   if (!target.value || target.length < 3) { return }
   if (event.which !== 13) { return }

   githubClient
     .getJSONRepos(target.value)
     .then((response) => {
       target.blur();
       this.setState({ ...this.state, value: 1 });
       this.resetRepos(response.items);
     })
 }
  renderRepos = (repos) => {
   if (!repos) { return [] }
   return repos.map((repo) => {
     return (
       <Grid item xs={12} md={3} key={repo.id}>
         <GithubRepo onKudo={this.onKudo} isKudo={this.isKudo(repo)} repo={repo} />
       </Grid>
     );
   })
 }

 render() {
   return (
     <div className={styles.root}>
       <SearchBar auth={this.props.auth} onSearch={this.onSearch} />
        <Tabs
         value={this.state.value}
         onChange={this.handleTabChange}
         indicatorColor="primary"
         textColor="primary"
         fullWidth
       >
         <Tab label="Kudos" />
         <Tab label="Search" />
       </Tabs>

       <SwipeableViews
         axis={'x-reverse'}
         index={this.state.value}
         onChangeIndex={this.handleTabChangeIndex}
       >
         <Grid container spacing={16} style={{padding: '20px 0'}}>
           { this.renderRepos(this.state.kudos) }
         </Grid>
         <Grid container spacing={16} style={{padding: '20px 0'}}>
           { this.renderRepos(this.state.repos) }
         </Grid>
       </SwipeableViews>
     </div>
   );
 }
}

export default withStyles(styles)(withAuth(Home));


Now run npm start and open http://localhost:8080 in your browser. You should be able to login, search for GitHub repos, and favorite a repo and see it in your Kudos list!

If you want to see what the finished project looks like, you can see the code on GitHub.

*Originally published by Kleber Correia at *developer.okta.com

===================================================================

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter

Learn More

☞ Complete Python Bootcamp: Go from zero to hero in Python 3

☞ Python and Django Full Stack Web Developer Bootcamp

☞ Python for Time Series Data Analysis

☞ Python Programming For Beginners From Scratch

☞ Beginner’s guide on Python: Learn python from scratch! (New)

☞ Python for Beginners: Complete Python Programming

☞ Django 2.1 & Python | The Ultimate Web Development Bootcamp

☞ Python eCommerce | Build a Django eCommerce Web Application

☞ Python Django Dev To Deployment

Verifying Sensitive Actions with Python, Flask and Authy

Verifying Sensitive Actions with Python, Flask and Authy

Verifying Sensitive Actions with Python, Flask and Authy. Adding two-factor authentication (2FA) to your login process increases the security of your user's data.

Verifying Sensitive Actions with Python, Flask and Authy. Adding two-factor authentication (2FA) to your login process increases the security of your user's data.

Adding two-factor authentication (2FA) to your login process increases the security of your user’s data. We can extend that to validate sensitive actions like sending money from your account, changing your shipping address, or confirming a medical appointment. Even though the user should be already logged in with a username and password, we want to make sure that they authorize every payment. This blog post will show you how to secure payment actions using Python, Flask, a bit of Javascript, and the Authy API.

PSD2 & SCA

The European Payment Services Directive (PSD2) regulation requires Strong Customer Authentication (SCA) for all transactions over €30 by September 2019. This post will show you how to implement a compliant solution for your application. For more detail on PSD2, SCA, and dynamic linking, check out this post.

The solution in this post is useful regardless of regulatory requirements. For example, Gemini uses push authorizations to validate cryptocurrency withdrawals.

What you’ll need

To code along with this post, you’ll need:

Setting Up

Start by downloading or cloning the starter application from Github.

git clone [email protected]:robinske/payfriend-starter.git && cd payfriend-starter

If you haven’t already, now is the time to sign up for Twilio and create an Authy Application. Navigate to the Twilio Console and grab your Authy App API Key under Settings.

Copy .env.example to .env. Once we have an Authy API key, we can store it in our .env file, which helps us set the environment variables for our app. Update your .env file:

# Secret key
SECRET_KEY=replace-me-in-production

# Authy API Key
# (create an app here https://www.twilio.com/console/authy)
AUTHY_API_KEY=FLc***************************

Installing dependencies

Next, install the necessary dependencies.

On Mac/Linux:

python3 -m venv venv

. venv/bin/activate

Or on Windows cmd:

py -3 -m venv venv

venv\Scripts\activate.bat

Install Requirements:

pip install -r requirements.txt

Now we’re ready to run and test our starter application.

Run the application

On Mac OS or Linux operating systems run:

export FLASK_APP=payfriend

export FLASK_ENV=development

flask run

Or on Windows cmd:

set FLASK_APP=payfriend

set FLASK_ENV=development

flask run

Navigate to http://127.0.0.1:5000/auth/register and register yourself as a new user with your real phone number. The application already has phone verification with the Twilio Verify API, which allows us to use the user’s trusted phone number for subsequent authorizations.

Phone verification is an important part of PSD2 compliance; we need to trust the device before we can start using it for payment authorizations.

After you’ve registered you’ll end up on a page like this:

At this point, you can send a payment of any amount to one of your friends. So if I send $20 to my friend Neville, I’ll see that reflected in My Payments:

What if we wanted additional safeguards to make sure I approved sending that money to Neville?

Registering a User with Authy

When a new user signs up for our website we store them in the database and register the user with Authy. This code is already in the starter project and you can learn more about that process in the Authy API documentation.

Once we register the user with Authy we get the user’s Authy ID from the response. This is very important — it’s how we will verify the identity of our user with Authy and send subsequent authorizations from our application.

How to Add PSD2 compliant 2FA for Payment Transactions

For this transaction, we will validate that the user has their mobile phone by either:

When a user attempts to “Send a Payment” on our website, we will ask them for an additional authorization. Let’s take a look at push authorization first.

Send the Push Request

When our user sends a payment we immediately attempt to verify their identity with a push authorization. We can fall back gracefully if they don’t have a smartphone with the Authy app, but we don’t know until we try.

Authy lets us pass extra details with our push request including a message, a logo, and any other details we want to send. We could easily send any number of details by adding additional key-value pairs to our details dict. For our scenario this could look like:

details = {
    'Sending to': 'Hermione',
    'Transaction amount': '1,000,000',
    'Currency': 'Galleons'
}

Details will show up in the app. Hidden details can also be included for tracking information about the request (like origin IP address) that you may not need or want to display in the app.

Let’s write the code for sending a push authorization. Head over to payfriend/utils.py and add the following function.

def send_push_auth(authy_id_str, send_to, amount):
    """
    Sends a push authorization with payment details to the user's Authy app

    :returns (push_id, errors): tuple of push_id (if successful)
                                and errors dict (if unsuccessful)
    """
    details = {
        "Sending to": send_to,
        "Transaction amount": str('${:,.2f}'.format(amount))
    }

    hidden_details = {
        "user_ip_address": request.environ.get('REMOTE_ADDR', request.remote_addr),
        "requester_user_id": str(g.user.id)
    }

    logos = [dict(res = 'default', url = 'https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/apple/155/money-bag_1f4b0.png')]

    api = get_authy_client()
    resp = api.one_touch.send_request(
        user_id=int(authy_id_str),
        message="Please authorize payment to {}".format(send_to),
        seconds_to_expire=1200,
        details=details,
        hidden_details={},
        logos=logos
    )

    if resp.ok():
        push_id = resp.content['approval_request']['uuid']
        return (push_id, {})
    else:
        flash(resp.errors()['message'])
        return (None, resp.errors())

This function constructs our push authorization request. We add necessary details about the request like the transaction amount and the payee. We also define the logo that will show up in the request. If the request is successful, resp.ok() will return True and we can grab the authorization uuid from the response. Otherwise we’ll return the relevant errors.

Next, head over to payfriend/payment.py and add the code to call our new function. In def send() we only want to add the Payment to the database once we have sent the push authorization. To do that, we’ll add a conditional statement after we try to send the push authorization. Replace lines 76-79 (starting with payment =) in the starter project with the highlighted code below.

def send():
    form = PaymentForm(request.form)
    if form.validate_on_submit():
        send_to = form.send_to.data
        amount = form.amount.data
        authy_id = g.user.authy_id

        # create a unique ID we can use to track payment status
        payment_id = str(uuid.uuid4())

        (push_id, errors) = utils.send_push_auth(authy_id, send_to, amount)
        if push_id:
            payment = Payment(payment_id, authy_id, send_to, amount, push_id)
            db.session.add(payment)
            db.session.commit()
            return jsonify({
                "success": True,
                "payment_id": payment_id
            })
        else:
            flash("Error sending authorization. {}".format(errors))
            return jsonify({"success": False})

    return render_template("payments/send.html", form=form)

Once we send the request we update the payment status based on the response. Let’s update our payments view to show the status. Open payfriend/templates/payments/list.html and add a column for the status:

<table style="width:100%">
  <tr>
    <th>Your Email</th>
    <th>Payment ID</th>
    <th>Sent to</th>
    <th>Amount</th>
    <th>Status</th>
  </tr>
{% for payment in payments %}
<tr>
  <td>{{ payment.email }}</td>
  <td>{{ payment.id }}</td>
  <td>{{ payment.send_to }}</td>
  <td>{{ "${:,.2f}".format(payment.amount) }}</td>
  <td>{{ payment.status }}</td>
</tr>
{% endfor %}
</table>

Make sure you have the Authy app installed on your phone and that you’re registered for Authy with the same phone number that you used to register for Payfriend. Restart the Flask application and send a payment. We’re not updating the status yet but this time you’ll get an authorization request in the Authy app and see a ‘pending’ status attached to that new payment ID.

Configure the push authorization callback

In order for our app to know what the user did after we sent the authorization request, we need to register a callback endpoint with Authy.

In payment.py add a new route for the callback. Here we look up the payment using the uuid sent with the Authy POST request.

def update_payment_status(payment, status):
    # once a payment status has been set, don't allow that to change
    # this requires a new transaction in order to be PSD2 compliant
    if payment.status != 'pending':
        flash("Error: payment request was already {}. Please start a new transaction.".format(
            payment.status))
        return redirect(url_for('payments.list_payments'))

    payment.status = status
    db.session.commit()

@bp.route('/callback', methods=["POST"])
@verify_authy_request
def callback():
    """
    Used by Twilio to send a notification when the user
    approves or denies a push authorization in the Authy app
    """
    push_id = request.json.get('uuid')
    status = request.json.get('status')
    payment = Payment.query.filter_by(push_id=push_id).first()

    update_payment_status(payment, status)
    return ('', 200)

We need one more endpoint that our client side code can query to check the payment status and update our view accordingly. Add this to payment.py:

@bp.route('/status', methods=["GET", "POST"])
@login_required
def status():
    """
    Used by AJAX requests to check the OneTouch verification status of a payment
    """
    payment_id = request.args.get('payment_id')
    payment = Payment.query.get(payment_id)
    return payment.status

Let’s take a look at that client-side code now.

Handle Two-Factor Asynchronously

We want to handle our authorization asynchronously so the user doesn’t even know it’s happening.

We’ve already taken a look at what’s happening on the server side, so let’s step in front of the cameras now and see how our JavaScript is interacting with those server endpoints.

First, we hijack the payment form submit and pass the data to our controller using Ajax. If we expect a push authorization response, we then begin polling /payment/status every 3 seconds until we see the request was either approved or denied. Our callback will update /payment/status so we will know when an authorization has been approved or denied.

In auth.js update the sendPayment function to check for the payment authorization status before redirecting the user.

var sendPayment = function(form) {
    $.post("/payments/send", form, function(data) {
      if (data.success) {
        $(".auth-ot").fadeIn();
        checkPaymentStatus(data.payment_id);
      }
    });
  };

var checkPaymentStatus = function(payment_id) {
    $.get("/payments/status?payment_id=" + payment_id, function(data) {
      if (data == "approved") {
        redirectWithMessage('/payments/', 'Your payment has been approved!')
      } else if (data == "denied") {
        redirectWithMessage('/payments/send', 'Your payment request has been denied.');
      } else {
        setTimeout(checkPaymentStatus(payment_id), 3000);
      }
    });
  };

You’ll need a publicly accessible route that Authy can access in order to handle the callback and for that we will use ngrok. Note that in this tutorial only the HTTP address from ngrok will work, so you should start it using this command:

ngrok http -bind-tls=false 5000

Copy the Forwarding url:

Head back to Authy Console and update your application’s Push Authentication callback URL with /payments/callback appended.

Leave ngrok running in the background and try sending a new payment. Now when you approve or deny the request your application will update the payment status and you can see it reflected in your list of payments.

Now let’s see how to handle the case where a user doesn’t have the Authy app installed.

Fallback to SMS

There are reasons that you could include SMS as a verification option: like being able to reach users without smartphones and onboard users seamlessly (no app install required).

In utils.py add the code to send and validate an authorization token via SMS. One important feature we’re taking advantage of here is the action and action_message parameters. The action will tie the SMS authorization to the specific transaction, a requirement for PSD2. The action message will add important details to the SMS message body about the payee and amount of the transaction, other requirements for PSD2 and a helpful message to the user regardless of regulation.

def send_sms_auth(payment):
    """
    Sends an SMS one time password (OTP) to the user's phone_number

    :returns (sms_id, errors): tuple of sms_id (if successful)
                               and errors dict (if unsuccessful)
    """
    api = get_authy_client()
    session['payment_id'] = payment.id
    options = {
        'force': True,
        'action': payment.id,
        'action_message': 'Verify Payment to {} for {}'.format(
            payment.send_to,
            str('${:,.2f}'.format(payment.amount)))
    }
    resp = api.users.request_sms(payment.authy_id, options)
    if resp.ok():
        flash(resp.content['message'])
        return True
    else:
        flash(resp.errors()['message'])
        return False


def check_sms_auth(authy_id, payment_id, code):
    """
    Validates an one time password (OTP)
    """
    api = get_authy_client()
    try:
        options = {
            'force': True,
            'action': payment_id,
        }
        resp = api.tokens.verify(authy_id, code, options)
        if resp.ok():
            return True
        else:
            flash(resp.errors()['message'])
    except Exception as e:
        flash("Error validating code: {}".format(e))
   
    return False

Note that you must include the same action parameter when sending and checking the token. We’re using the payment_id for that.

Next, add a new route for starting the SMS authorization in payments.py and a wrapper function for our validation utility:

@bp.route('/auth/sms', methods=["POST"])
@login_required
def sms_auth():
    if not g.user.authy_id:
        return(redirect(url_for('auth.verify')))

    payment_id = request.form['payment_id']
    session['payment_id'] = payment_id
    payment = Payment.query.get(payment_id)
   
    if utils.send_sms_auth(payment):
        return redirect(url_for('auth.verify'))
    else:
        return redirect(url_for('payments.send'))

def check_sms_auth(authy_id, payment_id, code):
    """
    Validates an SMS OTP.
    """
    if utils.check_sms_auth(g.user.authy_id, payment_id, code):
        payment = Payment.query.get(payment_id)
        update_payment_status(payment, 'approved')
        return redirect(url_for('payments.list_payments'))
    else:
        abort(400)

Finally we can take advantage of the existing token verification route we used for phone verification in auth.py with a few small changes.

Add an import for our new check_sms_auth function in auth.py:

from payfriend.payment import check_sms_auth

Then update the /verify route to check based on the type of verification. Right now this route only handles phone verification on signup before we’ve created the Authy user. Therefore we can assume that if the global user has an Authy ID then we can check the SMS authorization instead of the phone verification.

The new /verify route will look like this:

@bp.route('/verify', methods=('GET', 'POST'))
def verify():
    """
    Generic endpoint to verify a code entered by the user.
    """
    form = VerifyForm(request.form)
    validated = form.validate_on_submit()

    if form.validate_on_submit():
        email = g.user.email
        (country_code, phone) = utils.parse_phone_number(g.user.phone_number)
        code = form.verification_code.data

        # route based on the type of verification
        if not g.user.authy_id:
            if utils.check_verification(country_code, phone, code):
                return handle_verified_user(email, country_code, phone, code)
        else:
            payment_id = session['payment_id']
            return check_sms_auth(g.user.authy_id, payment_id, code)

    return render_template('auth/verify.html', form=form)

Finally, we need to update the client code to allow for SMS authorization. You can handle this many ways, but let’s make the Send SMS button appear after 15 seconds if the user hasn’t approved the push authorization in auth.js:

var sendPayment = function(form) {
  $.post("/payments/send", form, function(data) {
    if (data.success) {
      $(".auth-ot").fadeIn();
      checkPaymentStatus(data.payment_id);

      // display SMS option after 15 seconds
      setTimeout(function() {
        $("#payment_id").val(data.payment_id);
        $(".auth-sms").fadeIn();
      }, 15000);
    }
  });
};

Nice! Now you have a PSD2 compliant application with two different options for authorization.

Where to next?

The full code is available on my Github. Take a look at this diff to compare the finished solution with the starter solution.

Learn More

Complete Python Bootcamp: Go from zero to hero in Python 3

Complete Python Masterclass

Learn Python by Building a Blockchain & Cryptocurrency

Python and Django Full Stack Web Developer Bootcamp

The Python Bible™ | Everything You Need to Program in Python

Learning Python for Data Analysis and Visualization

Python for Financial Analysis and Algorithmic Trading

The Modern Python 3 Bootcamp