Authorization | React / Google w/ examples *

react basic authGoogle Auth Overview

https://jasonwatmore.com/post/2018/09/11/react-basic-http-authentication-tutorial-example

React – Basic HTTP Authentication Tutorial & Example

Tutorial built with React 16.5

Other versions available:

In this tutorial we'll cover how to implement a simple login page with React and Basic HTTP Authentication.

The project for the tutorial is available on GitHub at https://github.com/cornflourblue/react-basic-authentication-example.

Here it is in action:(See on StackBlitz at https://stackblitz.com/edit/react-basic-authentication-example)

Running the React Basic Auth Tutorial Example Locally

  1. Install NodeJS and NPM from https://nodejs.org/en/download/.
  2. Download or clone the project source code from https://github.com/cornflourblue/react-basic-authentication-example
  3. Install all required npm packages by running npm install from the command line in the project root folder (where the package.json is located).
  4. Start the application by running npm startfrom the command line in the project root folder, this will launch a browser displaying the application.

For more info on setting up a React development environment see React – Setup Development Environment.

Running the React Basic Auth Example with a Real Backend API

The React tutorial example uses a fake / mock backend by default so it can run in the browser without a real api, to switch to a real backend api you just have to remove or comment out the 2 lines below the comment // setup fake backend located in the /src/index.jsx file.

You can build your own backend api or start with one of the below options:

React Tutorial Project Structure

All source code for the React basic authentication tutorial is located in the /src folder. Inside the src folder there is a folder per feature (App, HomePage, LoginPage) and few folders for non-feature code that can be shared across different parts of the app (_components, _helpers, _services).

I prefixed non-feature folders with an underscore "_" to group them together and make it easy to distinguish between features and non-features, it also keeps the project folder structure shallow so it's quick to see everything at a glance from the top level and to navigate around the project.

The index.js files in each folder are barrel files that group all the exported modules together so they can be imported using the folder path instead of the full module path and to enable importing multiple modules in a single import (e.g. import { serviceOne, serviceTwo } from '../_services').

Click any of the below links to jump down to a description of each file along with it's code:

React Tutorial Components Folder

Path: /src/_components

The _components folder contains shared React components that can be used anywhere in the application.

Back to top

React Tutorial Private Route Component

Path: /src/_components/PrivateRoute.jsx

The react private route component renders a route component if the user is logged in, otherwise it redirects the user to the /login page.

The way it checks if the user is logged in is by checking that there is a user object in local storage. While it's possible to bypass this check by manually adding an object to local storage using browser dev tools, this would only give access to the client side component, it wouldn't give access to any real secure data from the server api because valid user credentials are required for this.

import React from 'react';
import { Route, Redirect } from 'react-router-dom';

export const PrivateRoute = ({ component: Component, ...rest }) => (
    <Route {...rest} render={props => (
        localStorage.getItem('user')
            ? <Component {...props} />
            : <Redirect to={{ pathname: '/login', state: { from: props.location } }} />
    )} />
)

Back to top

React Tutorial Helpers Folder

Path: /src/_helpers

The helpers folder contains all the bits and pieces that don't fit into other folders but don't justify having a folder of their own.

Back to top

React Tutorial Auth Header

Path: /src/_helpers/auth-header.js

Auth header is a helper function that returns an HTTP Authorization header containing the basic authentication credentials (base64 username and password) of the currently logged in user from local storage. If the user isn't logged in an empty object is returned.

The auth header is used to make authenticated HTTP requests to the server api using basic authentication.

export function authHeader() {
    // return authorization header with basic auth credentials
    let user = JSON.parse(localStorage.getItem('user'));

    if (user && user.authdata) {
        return { 'Authorization': 'Basic ' + user.authdata };
    } else {
        return {};
    }
}

Back to top

React Tutorial Fake / Mock Backend

Path: /src/_helpers/fake-backend.js

The fake backend is used for running the tutorial example without a server api (backend-less). It monkey patches the fetch() function to intercept certain api requests and mimic the behaviour of a real api. Any requests that aren't intercepted get passed through to the real fetch() function.

export function configureFakeBackend() {
    let users = [{ id: 1, username: 'test', password: 'test', firstName: 'Test', lastName: 'User' }];
    let realFetch = window.fetch;
    window.fetch = function (url, opts) {
        return new Promise((resolve, reject) => {
            // wrap in timeout to simulate server api call
            setTimeout(() => {

                // authenticate
                if (url.endsWith('/users/authenticate') && opts.method === 'POST') {
                    // get parameters from post request
                    let params = JSON.parse(opts.body);

                    // find if any user matches login credentials
                    let filteredUsers = users.filter(user => {
                        return user.username === params.username && user.password === params.password;
                    });

                    if (filteredUsers.length) {
                        // if login details are valid return user details
                        let user = filteredUsers[0];
                        let responseJson = {
                            id: user.id,
                            username: user.username,
                            firstName: user.firstName,
                            lastName: user.lastName
                        };
                        resolve({ ok: true, text: () => Promise.resolve(JSON.stringify(responseJson)) });
                    } else {
                        // else return error
                        reject('Username or password is incorrect');
                    }

                    return;
                }

                // get users
                if (url.endsWith('/users') && opts.method === 'GET') {
                    // check for fake auth token in header and return users if valid, this security 
                    // is implemented server side in a real application
                    if (opts.headers && opts.headers.Authorization === `Basic ${window.btoa('test:test')}`) {
                        resolve({ ok: true, text: () => Promise.resolve(JSON.stringify(users)) });
                    } else {
                        // return 401 not authorised if token is null or invalid
                        resolve({ status: 401, text: () => Promise.resolve() });
                    }

                    return;
                }

                // pass through any requests not handled above
                realFetch(url, opts).then(response => resolve(response));

            }, 500);
        });
    }
}

Back to top

React Tutorial Services Folder

Path: /src/_services

The _services layer handles all http communication with backend apis for the application, each service encapsulates the api calls for a content type (e.g. users) and exposes methods for performing various operations (e.g. CRUD operations). Services can also have methods that don't wrap http calls, for example the userService.logout() method just removes an item from local storage.

I like wrapping http calls and implementation details in a services layer, it provides a clean separation of concerns and simplifies the react components that use the services.

Back to top

React Tutorial User Service

Path: /src/_services/user.service.js

The user service encapsulates all backend api calls for performing CRUD operations on user data, as well as logging and out of the example application. The service methods are exported via the userService object at the top of the file, and the implementation of each method is located in the function declarations below.

In the handleResponse method the service checks if the http response from the api is 401 Unauthorized and automatically logs the user out. This handles if the credentials are incorrect or if the user is no longer valid for any reason.

import config from 'config';
import { authHeader } from '../_helpers';

export const userService = {
    login,
    logout,
    getAll
};

function login(username, password) {
    const requestOptions = {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username, password })
    };

    return fetch(`${config.apiUrl}/users/authenticate`, requestOptions)
        .then(handleResponse)
        .then(user => {
            // login successful if there's a user in the response
            if (user) {
                // store user details and basic auth credentials in local storage 
                // to keep user logged in between page refreshes
                user.authdata = window.btoa(username + ':' + password);
                localStorage.setItem('user', JSON.stringify(user));
            }

            return user;
        });
}

function logout() {
    // remove user from local storage to log user out
    localStorage.removeItem('user');
}

function getAll() {
    const requestOptions = {
        method: 'GET',
        headers: authHeader()
    };

    return fetch(`${config.apiUrl}/users`, requestOptions).then(handleResponse);
}

function handleResponse(response) {
    return response.text().then(text => {
        const data = text && JSON.parse(text);
        if (!response.ok) {
            if (response.status === 401) {
                // auto logout if 401 response returned from api
                logout();
                location.reload(true);
            }

            const error = (data && data.message) || response.statusText;
            return Promise.reject(error);
        }

        return data;
    });
}

Back to top

React Tutorial App Folder

Path: /src/App

The app folder is for react components and other code that is used only by the app component in the tutorial application.

Back to top

React Tutorial App Component

Path: /src/App/App.jsx

The app component is the root component for the react tutorial application, it contains the outer html, routes and global alert notification for the example app.

import React from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';

import { PrivateRoute } from '../_components';
import { HomePage } from '../HomePage';
import { LoginPage } from '../LoginPage';

class App extends React.Component {
    render() {
        return (
            <div className="jumbotron">
                <div className="container">
                    <div className="col-sm-8 col-sm-offset-2">
                        <Router>
                            <div>
                                <PrivateRoute exact path="/" component={HomePage} />
                                <Route path="/login" component={LoginPage} />
                            </div>
                        </Router>
                    </div>
                </div>
            </div>
        );
    }
}

export { App };

Back to top

React Tutorial Home Page Folder

Path: /src/HomePage

The home page folder is for react components and other code that is used only by the home page component in the tutorial application.

Back to top

React Tutorial Home Page Component

Path: /src/HomePage/HomePage.jsx

The home page component is displayed after signing in to the application, it shows the signed in user's name plus a list of all users in the tutorial application. The users are fetched from the api by calling the userService.getAll() method from the componentDidMount() react lifecycle hook.

import React from 'react';
import { Link } from 'react-router-dom';

import { userService } from '../_services';

class HomePage extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            user: {},
            users: []
        };
    }

    componentDidMount() {
        this.setState({ 
            user: JSON.parse(localStorage.getItem('user')),
            users: { loading: true }
        });
        userService.getAll().then(users => this.setState({ users }));
    }

    render() {
        const { user, users } = this.state;
        return (
            <div className="col-md-6 col-md-offset-3">
                <h1>Hi {user.firstName}!</h1>
                <p>You're logged in with React & Basic HTTP Authentication!!</p>
                <h4>Users from secure api end point:</h4>
                {users.loading && <em>Loading users...</em>}
                {users.length &&
                    <ul>
                        {users.map((user, index) =>
                            <li key={user.id}>
                                {user.firstName + ' ' + user.lastName}
                            </li>
                        )}
                    </ul>
                }
                <p>
                    <Link to="/login">Logout</Link>
                </p>
            </div>
        );
    }
}

export { HomePage };

Back to top

React Tutorial Login Page Folder

Path: /src/LoginPage

The login page folder is for react components and other code that is used only by the login page component in the tutorial application.

Back to top

React Tutorial Login Page Component

Path: /src/LoginPage/LoginPage.jsx

The login page component renders a login form with username and password fields. It displays validation messages for invalid fields when the user attempts to submit the form. If the form is valid the component calls the userService.login(username, password) method, if login is successful the user is redirected back to the original page they were trying to access.

In the constructor() function the userService.logout() method is called which logs the user out if they're logged in, this enables the login page to also be used as the logout page.

import React from 'react';

import { userService } from '../_services';

class LoginPage extends React.Component {
    constructor(props) {
        super(props);

        userService.logout();

        this.state = {
            username: '',
            password: '',
            submitted: false,
            loading: false,
            error: ''
        };

        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
    }

    handleChange(e) {
        const { name, value } = e.target;
        this.setState({ [name]: value });
    }

    handleSubmit(e) {
        e.preventDefault();

        this.setState({ submitted: true });
        const { username, password, returnUrl } = this.state;

        // stop here if form is invalid
        if (!(username && password)) {
            return;
        }

        this.setState({ loading: true });
        userService.login(username, password)
            .then(
                user => {
                    const { from } = this.props.location.state || { from: { pathname: "/" } };
                    this.props.history.push(from);
                },
                error => this.setState({ error, loading: false })
            );
    }

    render() {
        const { username, password, submitted, loading, error } = this.state;
        return (
            <div className="col-md-6 col-md-offset-3">
                <div className="alert alert-info">
                    Username: test<br />
                    Password: test
                </div>
                <h3>Login</h3>
                <form name="form" onSubmit={this.handleSubmit}>
                    <div className={'form-group' + (submitted && !username ? ' has-error' : '')}>
                        <label htmlFor="username">Username</label>
                        <input type="text" className="form-control" name="username" value={username} onChange={this.handleChange} />
                        {submitted && !username &&
                            <div className="help-block">Username is required</div>
                        }
                    </div>
                    <div className={'form-group' + (submitted && !password ? ' has-error' : '')}>
                        <label htmlFor="password">Password</label>
                        <input type="password" className="form-control" name="password" value={password} onChange={this.handleChange} />
                        {submitted && !password &&
                            <div className="help-block">Password is required</div>
                        }
                    </div>
                    <div className="form-group">
                        <button className="btn btn-primary" disabled={loading}>Login</button>
                        {loading &&
                            <img src="data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==" />
                        }
                    </div>
                    {error &&
                        <div className={'alert alert-danger'}>{error}</div>
                    }
                </form>
            </div>
        );
    }
}

export { LoginPage };

Back to top

React Tutorial Index HTML File

Path: /src/index.html

The base index html file contains the outer html for the whole tutorial application. When the app is started with npm start, Webpack bundles up all of the react code into a single javascript file and injects it into the body of the page.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>React - Basic HTTP Authentication Example & Tutorial</title>
    <link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
    <style>
        a { cursor: pointer; }
        .help-block { font-size: 12px; }
    </style>
</head>
<body>
    <div id="app"></div>
</body>
</html>

Back to top

React Tutorial Main Entry File

Path: /src/index.jsx

The root index.jsx file bootstraps the react tutorial application by rendering the App component into the app div element defined in the base index html file above.

The boilerplate application uses a fake / mock backend by default, to switch to a real backend api simply remove the fake backend code below the comment // setup fake backend.

import React from 'react';
import { render } from 'react-dom';

import { App } from './App';

// setup fake backend
import { configureFakeBackend } from './_helpers';
configureFakeBackend();

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

Back to top

React Tutorial Babel RC (Run Commands)

Path: /.babelrc

The babel config file defines the presets used by babel to transpile the React and ES6 code. The babel transpiler is run by webpack via the babel-loader module configured in the webpack.config.js file below.

{
  "presets": [
      "react",
      "env",
      "stage-0"
  ]
}

Back to top

React Tutorial Package.json

Path: /package.json

The package.json file contains project configuration information including package dependencies which get installed when you run npm install. Full documentation is available on the npm docs website.

{
  "name": "react-basic-authentication-example",
  "version": "1.0.0",
  "repository": {
    "type": "git",
    "url": "https://github.com/cornflourblue/react-basic-authentication-example.git"
  },
  "license": "MIT",
  "scripts": {
    "start": "webpack-dev-server --open"
  },
  "dependencies": {
    "react": "^16.0.0",
    "react-dom": "^16.0.0",
    "react-router-dom": "^4.1.2"
  },
  "devDependencies": {
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.5",
    "babel-preset-env": "^1.6.1",
    "babel-preset-react": "^6.16.0",
    "babel-preset-stage-0": "^6.24.1",
    "html-webpack-plugin": "^3.2.0",
    "path": "^0.12.7",
    "webpack": "^4.15.0",
    "webpack-cli": "^3.0.8",
    "webpack-dev-server": "^3.1.3"
  }
}

Back to top

React Tutorial Webpack Config

Path: /webpack.config.js

Webpack is used to compile and bundle all the project files so they're ready to be loaded into a browser, it does this with the help of loaders and plugins that are configured in the webpack.config.js file. For more info about webpack check out the webpack docs.

The webpack config file also defines a global config object for the application using the externals property, you can also use this to define different config variables for your development and production environments.

var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    mode: 'development',
    resolve: {
        extensions: ['.js', '.jsx']
    },
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                loader: 'babel-loader'
            }
        ]
    },
    plugins: [new HtmlWebpackPlugin({
        template: './src/index.html'
    })],
    devServer: {
        historyApiFallback: true
    },
    externals: {
        // global app config object
        config: JSON.stringify({
            apiUrl: 'http://localhost:4000'
        })
    }
}

Back to top





https://developers.google.com/workspace/guides/auth-overview

Authentication and authorization overview

Authentication (authn) and authorization (authz) are mechanisms used to verify the user of your app and their access to resources, respectively. This document identifies key terms that you should know before implementing authentication and authorization in your apps. The following diagram shows key components of an authentication and authorization implementation.

Note: This overview focuses on authentication and authorization when only using Google Workspace APIs. Some information in this document does not pertain to other APIs, such as Google Cloud or Google Maps APIs.

Figure 1 shows the high-level steps of an authentication and authorization implementation:

High-level steps of an authentication and     authorization implementationFigure 1. High-level steps of an authentication and authorization implementation

  1. (not shown) During development, you register the application in the Google Cloud Console and obtain credentials known only by your app and Google. You also configure a consent screen. This screen is shown to users of your app to gain their consent on the scope(s) of access your app needs to their Google resources.
  2. The app might also require the user to authenticate by signing in.
  3. When your app needs access to Google resources, it asks Google for the scope(s) of access needed to access the resources. For example, the app might need to read metadata of the user's files in Google Drive, so it would only request a metadata read-only scope.
  4. Google displays the consent screen.
  5. If the user consents to the scopes of access, your app bundles the credentials and the user-approved scopes of access into a request. The request is sent to the Google Authorization Server to obtain an access token.
  6. Google returns an access token with a list of scopes of access granted by the token. If the returned list of scopes is more limited than the requested scopes of access, the app disables any features limited by the token.
  7. Use the access token to invoke the API and access user data.
  8. (optional) If your application needs access to a Google API beyond the lifetime of a single access token, it can obtain a refresh token.
  9. If additional access is needed by your app, your app asks the user to grant new scopes of access, resulting in a new request to get an access token (steps 3-6).

Terms

Following is a list of terms related to authentication and authorization:

  • Authentication

    The act of ensuring that a principal, which can be a user or an app acting on behalf of a user, is who they say they are. When writing Google Workspace apps, you should be aware of these types of authentication:User authenticationThe act of a user authenticating (signing in) to your app. User authentication is usually carried out through a signing in process in which the user uses a username and password combination to verify their identity to the app. User authentication can be incorporated into an app using Google Sign-in .App authenticationThe act of an app authenticating directly to Google services on behalf of the user running the app. App authentication is usually carried out using pre-created credentials in your app's code.

  • Authorization

    The permissions or "authority" the principal has to access data or perform operations. The act of authorization is carried out through code you write in your app. This code informs the user that the app wishes to act on their behalf and, if allowed, uses your app's unique credentials to obtain an access token from Google used to access data or perform operations.

  • Credential

    A form of identification used in software security. In terms of authentication, a credential is often a username/password combination. In terms of authorization for Google Workspace APIs, a credential is usually some form of identification, such as a unique secret string, known only between the app developer and the authentication server. Google supports these authentication credentials: API key, OAuth 2.0 Client ID, and service accounts.API keyThe credential used to request access to public data, such as data provided using the Maps API. API keys can also be used to access Google Workspace files that are shared using "Anyone on the Internet with this link can (view/edit/comment)" setting within Google Workspace sharings settings.OAuth 2 client IDThe credential used to request access to user-owned data. This is the primary credential used when requesting access to data using Google Workspace APIs. This credential requires user consent.Client secretA string of characters that should only be known by your application and the authorization server. The client secret protects the user's data by only granting tokens to authorized requestors. You should never include your client secret in your app.Service account keysUsed by service accounts to authorize to Google service.Service accountA credential used for server-to-server interactions, such as a faceless app that runs as a process to access some data or perform some operation. Service accounts are usually used to access cloud-based data and operations. However, when used with domain delegation of authority, they can be used to access user data.

  • Scope

    A string defining a level of access to resources required by your app, such as the level of access to data owned by the user. Your app requests one or more scopes on behalf of the user. The authentication server determines which of your requested scopes it permits and returns those scopes in an access token. The "recommended" scopes represent limited access to resources and operations, while "restricted" scopes represent broader access.

  • Authorization server

    Google's server for granting access, using an access token, to an app's requested data and operations.

  • Authorization code

    A code sent from the Authorization server used to obtain an access token. A code is only needed when your application type is a web server app or an installed app.

  • Access token

    A token granting access to a Google Workspace API. A single access token can grant varying degrees, known as scopes, of access to multiple APIs. Your app's authorization code requests access tokens and uses them to invoke Google Workspace APIs.

  • Resource server

    The server hosting the API that your app wants to call.

  • OAuth 2.0 framework

    A standard that your app can use to provide it with “secure delegated access” or access to data and operations on behalf of the app's user. The authentication and authorization mechanisms you use in your app represent your implementation of the OAuth 2.0 framework.

  • Principal

    The entity attempting to authenticate to something. A principal can either be a user or an app acting on behalf of a user.

  • Data type

    In the context of authentication and authorization, data type refers to the entity that owns the data that your app is trying to access. There are three data types:Public domain dataData accessible by anyone, such as some Google maps data. This data is usually accessed using an API key.End-user dataData belonging to a specific end user or group, such as a specific user's Google Drive files. This data type is usually accessed using an OAuth 2 client ID or service account.Cloud dataData owned by a cloud project. This data type is usually accessed by a service account.

  • User consent

    An authorization step requiring the user of your app to authorize the app to access data and operations on the user's behalf.

  • Application type

    The type of app you are going to create. When creating credentials using the Google Cloud Console, you are asked to select your application type. Application types are: Web application (JavaScript), Android, Chrome app, iOS, TVs and Limited Input devices, Desktop app (also called an "installed app"), and Universal Windows Platform (UWP).

  • Service account

    A special type of Google account intended to represent a non-human user that needs to authenticate and be authorized to access data. Your application assumes the identity of the service account to call Google APIs, so that the users aren't directly involved. By themselves, service accounts cannot be used to access user data; data customarily accessed using Workspace APIs. However, a service account can access user data by implementing domain-wide delegation of authority. For further information, refer to Understanding service accounts

  • Domain-wide delegation of authority

    An Administration feature that allows apps to access users' data across your organization's Google Workspace environment. Domain-wide delegation allows the app to perform admin-related tasks on user data. Because of the power of this feature, only super admins can enable domain-wide delegation of authority.

Scroll to Top