@frappy/react-authentication

Authentication related components for React

Usage no npm install needed!

<script type="module">
  import frappyReactAuthentication from 'https://cdn.skypack.dev/@frappy/react-authentication';
</script>

README

React Authentication Module

React Pages and Components to Facilitate Authentication and User Management

  1. Login and Authentication
    1. Login-Protected Page Section
    2. Login Form Only
    3. Logout
    4. Change Password
  2. Login and Permission Check
    1. Permission Check
    2. Login Check
  3. User Management
    1. User Profiles
  4. API Keys
  5. API Endpoints
    1. UserHandler

This package is a counterpart to the following server-side packages and uses the endpoints provided by those packages:

Login and Authentication

Login-Protected Page Section

Simply wrap your application entry point in the LoginWrapper component provided by this package. This will perform an authentication check to the API and if successful render the children of the component. If the authentication check fails, the login form will be rendered.

import React from "react"
import { LoginWrapper } from "@frappy/react-authentication"

class MyPage extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            currentUser: null,
        }
        this.receiveUser = this.receiveUser.bind(this)    
    }
    
    receiveUser(user) {
        this.setState({
            currentUser: user,
        })
    }   

    render() {
        return (
            <LoginWrapper setUser={this.receiveUser}>
                <div>Put your app content / component here</div>
            </LoginWrapper>
        )
    }
}

render(<MyPage/>, document.getElementById("root"))

You can obviously put the LoginWrapper at any level of your application (e.g. to just protect certain areas).

Properties

  • setUser - default null - function to receive the logged in user, either after the initial auth check, if the user is already authenticated or after successful login
  • apiPrefix - default /api/user - endpoint used for authentication check and login (login endpoint uses ${apiPrefix}/login)

Login Form Only

If you only need authentication for specific areas, you can simply use the LoginForm itself and embed it into your page:

import React from "react"
import { LoginForm } from "@frappy/react-authentication"

class MyPage extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            currentUser: null
        }       
        this.setUser = this.setUser.bind(this)
    }
    
    setUser(user) {
        this.setState({
            currentUser: user,
        })
    }   

    render() {
        return (
            <div>
                <LoginForm setUser={this.setUser} apiPrefix="/api/user" />
            </div>        
        )            
    }   
}

Properties

  • setUser - a function to handle the incoming user object, if authentication is successful
  • apiPrefix - default /api/user - the prefix for the /login endpoint, default will result in POST /api/user/login

Logout

This package also provides a logout component, which facilitates the log out. The content (e.g. an icon or text) needs to be provided by you. A click on it will call the logout endpoint, delete the browser session token and reload the page:

import React from "react"
import { Logout } from "@frappy/react-authentication"

const Header = props => (
    <div>
        <Logout>Log me out</Logout>
    </div>
)

Properties

  • apiPrefix - default /api/user - will result in logout REST call: DELETE /api/user/login

Change Password

The package provides a simple change password form, where the user needs to provide the current password and set the new password (twice for confirmation and to avoid typos). The component ties into the change password endpoints provided by frappyflaskauth (Python) or @frappy/node-authentication (Node).

import React from "react"
import { ChangePassword } from "@frappy/react-authentication"

const UserManagementPage = props => (
    <div>
        <ChangePassword currentUser={props.currentUser} />
    </div>
)

Properties

  • currentUser - the currently logged in user (as provided by the LoginForm or LoginWrapper)
  • apiPrefix - default /api/user - the prefix for the change password API call
  • mixins - default quick-n-dirty-react/mixins - mixins user to style the textInput, form label and button.
  • titleStyle - default to red, bold title font - an override for the style of the form title "Change Password"

Permission and Login Check

Permission Check

If you have a section of the page that requires special permissions, you can protect it like this:

import React from "react"
import { PermissionCheck } from "@frappy/react-authentication"

const MyProtectedComponent = props => (
    <PermissionCheck currentUser={props.currentUser} requiredPermissions="control" showError>
        <div>Protected content that is only shown, if the user has "control" permission</div>
    </PermissionCheck>
)

Properties

  • currentUser - the currently logged in user (permission check checks the currentUser.permissions array)
  • requiredPermissions - default null - there are 3 ways to configure this:
    • if you omit the parameter, the user simply has to be authenticated (currentUser is not null)
    • you can provide a single required permission as a string or
    • you can provide multiple permissions as array
  • showError - default false - if the user fails the permission / login check an error message will be shown informing the user that they do not have access to the content.

Login Check

The LoginCheck can also be used standalone to use an existing auth token (stored in the browser's sessionStorage) to check whether that auth token is valid and fetch the associated user from the API.

The usage is equivalent to the usage of the LoginForm (see Login Form Only):

import { LoginCheck } from "@frappy/react-authentication"

class MyComponent extends React.Component {
    ...
    render() {
        return (
            <div>
                <LoginCheck setUser={this.setUser} />
                Some more content that is displayed regardless of login check success.
            </div>
        )
          
    } 
}

The setUser method will receive null, if the user is not logged in (no auth token in browserStorage or auth token invalid / expired)

Properties

  • setUser - handler for receiving logged in user or null after the check is completed
  • apiPrefix - default /api/user - endpoint to check for the auth status

User Management

The UserManager can directly be embedded into your Router declarations. The component will check the user permissions. The currentUser is the user received from the LoginWrapper and can provide a permissions array with desired administrative permission (see properties below for details)

import React from "react"
import { Switch, Route } from "react-route"
import { UserManager } from "@frappy/react-authentication"

const RouterComponent = props => (
    <Switch>
        <Route path="/admin/users" exact component={() => <UserManager currentUser={props.currentUser}/>} />
        ... other routes
    </Switch>
)

Properties

  • currentUser - required - a user object containing a permissions array with the admin permission. The component will only be rendered, if the admin permission is available
  • adminPermission - default admin - required permission (see above) to render the component. If the permission is not available within the currentUser.permissions, an error message about missing permissions will be returned.
  • apiPrefix - default /api/user/users - base endpoint for all user management operations. Administrative endpoints will effectively be: /api/user/users to retrieve users, and /api/user/users/:userId for user specific operations.
  • permissions - default [] - list of other permissions (beside the admin permission) that will be available in the system
  • profileManager - default null - see User Profiles

User Profiles

You can integrate a profile manager into the UserManager by providing a React component class as property profileManager.

Example of integration:

import MyCustomReactComponent from "./MyCustomReactComponent"

...
<UserManager ... profileManager={MyCustomReactComponent} profileManagerProps={{ 
    some: {
        custom: "parameters",
        or: "payload",
    },
}} />
...

Additional properties can be passed in using the property profileManagerProps, which will be handed down to your custom class as profileManagerProps.

Example of custom profile manager

import React from "react"

class MyCustomReactComponent extends React.Component {
    getProfile() {
        return {
            name: this.formElement.value
        }   
    }   

    render() {
        console.log("Profile manager properties passed in from the user manager", this.props.profileManagerProps)
        return (
            <div><input type="text" ref={el => { this.formElement = el }} defaultValue={this.props.user.profile.name} /></div>
        )        
    }
}

You can render any content in the render method, where you can use this.props.user to access the user to be edited. Additionally, your class must provide a getProfile method, which returns a JSON object. This is used by the user manager to extract the profile information from the form.

In the example above, if we have a user, with user.profile.name = "frappy", the form would show a text input with "frappy" as default value. If you change it to "foobar" and press "save", the getProfile method would return { name: "foobar" } which would be used as new user.profile, so that user.profile.name = "foobar".

API Keys

Frappy also supports API keys, which can be used to authenticate against endpoints using a permanent non-expiring API key. This requires the server-side to enable API keys (Node: apiKeys: true, Python: api_keys: True to pass in as option for the user endpoint registration).

To manage and fetch the API key on an individual user basis, you can use the <ApiKeyForm /> React component and make it accessible for each logged in user. This form allows the user to create or revoke an API key or fetch it to be used in a program.

import React from "react"
import { Switch, Route } from "react-route"
import { ApiKeyForm } from "@frappy/react-authentication"

const RouterComponent = props => (
    <Switch>
        <Route path="/user/api-key" exact component={() => <ApiKeyForm currentUser={props.currentUser}/>} />
        ... other routes
    </Switch>
)

Properties

  • currentUser - required - a user object for which the API key will be managed.

API Endpoints

The following assumes the default prefix /api/user. URL parameter are prefixed with :

  • GET /api/user - authentication check
  • POST /api/user/login - performs authentication check with username and password in the body
  • DELETE /api/user/login

User Management Endpoints

  • POST /api/user/users/:userId/permissions - updates the user's permissions with permissions array in the body.
  • DELETE /api/user/users/:userId - deletes the given user
  • POST /api/user/users - creates a new local user, with username, password and permissions in the body.
  • POST /api/user/users/:userId/profile - updates the user's profile with the provided body (full JSON body will be used)

UserHandler

Administrative functions can be performed using the UserHandler helper class:

import { UserHandler } from "@frappy/react-authentication"

const handler = new UserHandler()  // allows for one parameter for apiPrefix - default: "/api/user/users"
handler.updatePermissions(userId, ["admin", "view"]).then(updatedUser => {
    console.log("Updated user with new permissions:", updatedUser)
})
handler.updateUserProfile(userId, { foo: "bar" }).then(updatedUser => { ... })

Methods

  • updateUserProfile(userId, profile) - to update a user profile, returns the updated user when the promise resolves.
  • deleteUser(userId) - purges a user from the database
  • getAllUsers(page) - returns a list of all users when the promise resolves (requested page given servers page size)
  • updatePermissions(userId, permissions) - sets new permissions for the provided user and returns the updated user when the promise resolves.
  • createUserRequest(newUser) - runs a request for creating a new user and returns the promise with the response object
  • createUser(newUser) - calls createUserRequest and also handles the REST response, in case no special error handling is needed.
  • updateUserPassword - updates another user's password, requires admin privileges on the server-side.