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:
- React: React + Recoil
- Vue: Vue.js
- Angular: Angular 10, 9, 8, 6
- Next.js: Next.js 11
- AngularJS: AngularJS
- Blazor: Blazor WebAssembly
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
- Install NodeJS and NPM from https://nodejs.org/en/download/.
- Download or clone the project source code from https://github.com/cornflourblue/react-basic-authentication-example
- Install all required npm packages by running
npm install
from the command line in the project root folder (where the package.json is located). - Start the application by running
npm start
from 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:
- To run the React basic auth example with a real backend API built with Node.js follow the instructions at NodeJS – Basic Authentication Tutorial with Example API
- For a real backend API built with ASP.NET Core 2.1 follow the instructions at ASP.NET Core 2.1 – Basic Authentication Tutorial with Example API
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:
src
_components
- PrivateRoute.jsx
- index.js
_helpers
- auth-header.js
- fake-backend.js
- index.js
_services
- user.service.js
- index.js
App
- App.jsx
- index.js
HomePage
- HomePage.jsx
- index.js
LoginPage
- LoginPage.jsx
- index.js
React Tutorial Components Folder
Path: /src/_components
The _components folder contains shared React components that can be used anywhere in the application.
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 } }} />
)} />
)
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.
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 {};
}
}
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);
});
}
}
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.
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;
});
}
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.
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 };
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.
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 };
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.
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 };
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>
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')
);
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"
]
}
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"
}
}
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'
})
}
}
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:
Figure 1. High-level steps of an authentication and authorization implementation
- (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.
- The app might also require the user to authenticate by signing in.
- 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.
- Google displays the consent screen.
- 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.
- 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.
- Use the access token to invoke the API and access user data.
- (optional) If your application needs access to a Google API beyond the lifetime of a single access token, it can obtain a refresh token.
- 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.
-
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.