react-express-oauth-login-system

React Login System

!! For the latest version see https://github.com/syntithenai/lambda-oauth-login-system

This package provides an easy way to add user registration and login to a React web application.

This package is an example application, that shows how to use the two related packages

It integrates a complete oauth2 server implementation and uses that for local authentication and token generation so passwords are never given to the web clients.

The delegated authentication provided by the oauth2 server is useful to allow third party web sites granular access to your application data.
For example, a public facing oauth server is required when developing apps for Google Home or Amazon Alexa that require user identification and account linking.

It also integrates passport.js to enable login using Google, Twitter, Facebook and Github. Passport includes solutions for many more authentication providers.

Features

Quickstart

The demo assumes there is a mongodb server running on localhost. See .env file and src/config.js for details.

To see the suite in action

git clone https://github.com/syntithenai/react-express-oauth-login-system.git
cd react-express-oauth-login-system/
npm i
npm start

Open https://localhost:3000/

Integration into your application

  1. Install the package from npm
npm i express-oauth-login-system-routes
npm i react-express-oauth-login-system-components
  1. Add the provided routes to your express server.
    • /src/index.js provides an example of integrating the login system routes.
    let config = require('./config');
    var loginSystem = require('express-oauth-login-system-server')
    var login = loginSystem(config)
    const loginRouter = login.router
    const authenticate = login.authenticate
    const csrf = login.csrf
    
    const app = express();
    app.use('/',function(req,res,next) {
        csrf.setToken(req,res,next)
    });
    app.use(bodyParser.json());
    app.use(cookieParser());
    // session required for twitter login
    app.use(session({ secret: config.sessionSalt ? config.sessionSalt : 'board GOAT boring do boat'}));
    app.use('/api/login',loginRouter);
    // endpoint requiring authentication
    app.use('/protected',authenticate,otherRoutesNeedingProtection);
    
     app.listen(port, () => {
      console.log(`Login system example listening at http://localhost:${port}`)
    })

    
  1. Use the LoginSystemContext at the root of your app to provide context to store the current logged in user and make it available as props down through your component tree.

Use LoginSystem component on the login react dom route (eg /login) in your React application.

Note that the LoginSystemContext exposes the current user and a bunch of helper functions to it’s child renderer.

import {LoginSystem,LoginSystemContext, getAxiosClient,getMediaQueryString,getCsrfQueryString} from 'react-express-oauth-login-system-components'

<LoginSystemContext  
        authServer={process.env.REACT_APP_authServer} 
         authServerHostname={process.env.REACT_APP_authServerHostname} 
    >
    {(user,setUser,getAxiosClient,getMediaQueryString,getCsrfQueryString, isLoggedIn, loadUser, useRefreshToken, logout, authServer, authServerHostname) => {
      return  <Router>
                <div style=>
                    <img style= src={csrfMediaImage} alt='csrf' />
                    <img style=  src={protectedMediaImage} alt='Not logged in' />
                    {!isLoggedIn() && <a href="/login/login"><button className='btn btn-primary'>Login</button></a>}
                    {isLoggedIn() && <a href="/login/profile"><button className='btn btn-primary'>Profile</button></a>}
                    <hr style=/>
                    <Route path='/login'  render={
                    (props) => <LoginSystem  
                       match={props.match}
                       location={props.location}
                       history={props.history}
                       authServer={authServer} 
                        // also need external link to auth server (combind authServerHostname + authServer) for google, github, .. login buttons
                        authServerHostname={authServerHostname} 
                        // update for login api location, use package.json proxy config to map other host/port to local link
                       loginButtons={process.env.REACT_APP_loginButtons?process.env.REACT_APP_loginButtons.split(","):[]}
                        // optional callbacks
                        logoutRedirect={'/'}
                       user={user} setUser={setUser} isLoggedIn={isLoggedIn} logout={logout} saveUser={saveUser} startWaiting={that.startWaiting} stopWaiting={that.stopWaiting} 
                     />}
                     />
                </div>
           </Router>
        }
    }
</LoginSystemContext>

The email templates for registration and forgot password can be set in config.

To make layout changes, extend the LoginSystem class and override render.

  1. Protecting your web API’s

All requests to your secured API endpoints must include an Authorization header including a bearer token

	fetch(that.props.authServer+'/saveuser', {
	  method: 'POST',
	  headers: {
		'Content-Type': 'application/json',
		'Authorization': 'Bearer '+user.token.access_token
	  },
	  body: JSON.stringify(user)
	})

The module exports a number of helper functions to React including getAxiosClient that adds authentications and csrf headers to ajax requests automatically.

import {getCookie,getAxiosClient} from './helpers'  
this.axiosClient = getAxiosClient(bearerToken);  
that.axiosClient( {
  url: that.props.authServer+'/protectedurl',
  method: 'post',
})
.then(function(res) {
  return res.data;  
})
.then(function(user) {})

Login With External Services

To enable login using external services you will need a key and secret from each of the services. Obtaining these keys may include filling a number of forms to justify your use of their API.

Keys are added to the config.js file.

To request keys visit the following links.

https://github.com/settings/applications

https://developer.twitter.com/

https://developers.facebook.com/apps/

https://console.developers.google.com/

https://developer.amazon.com/settings/console/securityprofile/overview.html

Authorizing external services

External services can use the oauth routes to obtain a token to access your API directly. In developing skills for Alexa(https://developer.amazon.com/) or Google Actions (https://console.actions.google.com/), the project administration website allows for entering

The authorization and token urls are immediately under the path that you located the loginsystem express routes. For example

[Some services also allow a choice between implicit and authorization code flows. Use authorization code.]

In the mongo database, create an entry in the oauthclients collection for each external service that can authenticate.

Client entries can also include fields to be used on the authorization page that is loaded when the external service redirects to your website to ask the user permission to grant access.

mongo browserexample
> db.users.insert({"_id" : ObjectId("5c859a7a64997a72a107065b"), "clientId" : "newclient", "clientSecret" : "testpass", "name" : "New Client", "website_url" : "https://client.com", "privacy_url" : "https://client.com/privacy", "grants" : [ "authorization_code", "password", "refresh_token", "client_credentials" ], "redirectUris" : []})

The server creates an initial client based on configuration settings for local authentication purposes. Examine that item for details.

mongo browserexample
> db.oauthclients.find()
{ "_id" : ObjectId("5c859a7a64997a72a107065b"), "clientId" : "test", "clientSecret" : "testpass", "name" : "Test Client", "website_url" : "https://localhost", "privacy_url" : "https://localhost/privacy", "grants" : [ "authorization_code", "password", "refresh_token", "client_credentials" ], "redirectUris" : [ ], "__v" : 0 }

Cross Site Request Forgery (CSRF) Protection

The example provides code to protect against Cross Site Request Forgery by