<commit_before>ATTENTON: For now, this must be considered experimental software and not to be used in production.

React Devise
=========================
For some time Devise has been the go-to authentication library for Rails apps. Just drop Devise into your Rails app, make a few tweaks, then get on with building the awesome business features of your app. But when we develop an SPA  app with React for the front end and Rails for the API, must we leave Devise behind?

It turns out it's not very hard to purpose Devise as an authentication backend. The bigger job is to replicate all the view-related functionality Devise gave us out of the box for server rendered apps.

Enter **React Devise**. The goal of this library is to reduce the friction of adding authentication to a new React/Rails app. We aim to reduce it to the very low level that Rails developers have come to expect while maintaining flexibility to make it your own.

## Dependencies

React Devise has deep dependencies on some popular React modules. The most significant are:

* [react-redux](https://github.com/reactjs/react-redux)
* [react-router](https://github.com/ReactTraining/react-router)
* [redux-form](https://github.com/erikras/redux-form)

If you don't want to use these in your app, then React Devise is probably not for you.

## Installation

```
yarn add react-devise
```

## Basic Usage

Call ```initReactDevise``` as early as possible in your application, and before using any other part of React Devise. This function returns a function which returns the config object.

Add ```reactDeviseReducers``` to your store.

Within the ```Router``` element place ```AuthRoutes```. Set the path to ```clientResourceName``` to cause the router to select among the full auth routes.

Use ```PrivateRoute``` for any route that requires authorization. If the user visits a private route while not authenticated, he will be redirected to the login route.

```javascript
import {createStore, combineReducers} from 'redux';
import {Provider} from 'react-redux';
import {Router, Route, Switch} from 'react-router-dom';
import createBrowserHistory from 'history/createBrowserHistory';
import reducers from './reducers'
import {initReactDevise, AuthRoutes, PrivateRoute} from 'react-devise';
import reactDeviseReducers from 'react-devise/lib/reducers';

const {clientResourceName} = initReactDevise()();

const store = createStore(
  combineReducers({
    ...reducers,
    ...reactDeviseReducers
  })
);

const App = () => {
  return (
    <Provider store={store}/>
      <Router history={createBrowserHistory()}>
        <Switch>
          <PrivateRoute exact path="/products" component={Products} />
          <Route exact path="/" component={Home} />
          <AuthRoutes path={`/${clientResourceName}`} />
          <Route component={NotFound} />
        </Switch>
      </Router>
    </Provider>
  );
}
```

Given that the default value of ```clientResourceName``` is "users" the default auth routes are:

* /users/login
* /users/sign-up/
* /users/confirmation/new
* /users/confirmation
* /users/password/new
* /users/password/edit

## Customization

To customize the appearance and behavior of React Devise, pass a settings object into ```initReactDevise```.

```javascript
import ReactDeviseMaterialUI from 'react-devise-material-ui';
import {Form, Alert, UnstyledList, UnstyledListItem, FormError, AuthHeading, AuthViewContainer} from '../components';

const myCustomPlugin = {
  Form,
  FormError,
  Alert,
  AuthLinksList: UnstyledList,
  AuthLinksListItem: UnstyledListItem,
  Heading: AuthHeading,
  View: AuthViewContainer
};

initReactDevise({
  clientResourceName: 'customers',
  apiResourceName: 'api/auth',
  apiHost: 'http://auth.example.com',
  viewPlugins: [
    ReactDeviseMaterialUI.plugin(),
    myCustomPlugin
  ],
  messages: {
    loginFailed: 'Whoa there. Bad login!'
  }
});
```

| Setting                     | Default Value | Description                                                                              |
| ----------------------------|---------------|------------------------------------------------------------------------------------------|
| `clientResourceName`        | "users"       | The first node in the route to each auth view. |
| `apiResourceName`           | "auth"        | The resource name used by Devise on the server. The first node in the path of API calls. |
| `apiHost`                   | `undefined`   | Omit unless your devise API is host on a different domain than the website. |
| `viewPlugins`               | []            | Use one or more view plugins to inject custom components into React Devise views.<br><br>View plugins are merged in order *after* the default plugin. Taking the code sample above, ```myCustomPlugin``` supersede ```ReactDeviseMaterialUI``` plugin, which in turn supersede the default plugin. |
| `defaultViewPluginSettings` | {}            | To customize the default plugin, provide a settings object. This can be used in conjunction with custom plugins or without them.<br><br>To see the available settings, find the default plugin [here]( https://github.com/timscott/react-devise/blob/master/src/config/viewPluginPlain.js). |
| `messages`                  | {}            | Override the default messages used by React Devise. Default messages are [here](https://github.com/timscott/react-devise/blob/master/src/config/defaultMessages.js). |


### Styled Components

React Devise plays nicely with [styled-components](https://github.com/styled-components/styled-components). For example, ```UnstlyedList``` (in the prior code sample) might be:

```javascript
import styled from 'styled-components';

const UnstyledList = styled.ul`
  list-style: none;
  padding: 0;
  a {
    text-decoration: none;
  }
  a:hover {
    text-decoration: underline;
  }
`;

export default UnstyledList;
```

## Accessing Configuration in Your Components

You can access React Devise config and also the ```AuthLinks``` component in your components by using ```withAuth``` like so:

```javascript
import React from 'react';
import {connect} from 'react-redux';
import {withAuth} from 'react-devise';

const Home = ({currentUser, auth: {AuthLinks}}) => {
  return (
    <div>
      <div>
        <h2>Welcome {currentUser && currentUser.email}</h2>
        <AuthLinks />
      </div>
    </div>
  );
};

const mapStateToProps = state => {
  return {
    currentUser: state.currentUser
  };
};

export default connect(mapStateToProps)(withAuth(Home));
```

## Middleware for Clients

Clients like [Apollo](https://github.com/apollographql/apollo) handle data access for you. React Devise provides middleware to add authentication to all requests. For example:

```javascript
import ApolloClient, {createNetworkInterface} from 'apollo-client';
import {addAuthorizationHeaderToRequest} from 'react-devise';

const networkInterface = createNetworkInterface({
  uri: '/graphql'
});

networkInterface.use([{
  applyMiddleware: addAuthorizationHeaderToRequest
}]);
```

```addAuthorizationHeaderToRequest``` takes a ```request``` and an optional ```next``` callback.

## Devise Server Setup

Eventually we want to create a gem to avoid repeating this boilerplate. For now, you have to set it up yourself.

First, we might need to set a custom path in our routes to match the ```apiResourceName``` if it's not the same as your user model name.

```ruby
# routes.rb

devise_for :users, path: :auth
```

Next, we need to change auth failure behavior:

```ruby
# /app/controllers/custom_auth_failure.rb

class CustomAuthFailure < Devise::FailureApp

  def respond
    self.status = :unauthorized<commit_msg>Added some missing steps in the Devise setup docs.<commit_after>ATTENTON: For now, this must be considered experimental software and not to be used in production.

React Devise
=========================
For some time Devise has been the go-to authentication library for Rails apps. Just drop Devise into your Rails app, make a few tweaks, then get on with building the awesome business features of your app. But when we develop an SPA  app with React for the front end and Rails for the API, must we leave Devise behind?

It turns out it's not very hard to purpose Devise as an authentication backend. The bigger job is to replicate all the view-related functionality Devise gave us out of the box for server rendered apps.

Enter **React Devise**. The goal of this library is to reduce the friction of adding authentication to a new React/Rails app. We aim to reduce it to the very low level that Rails developers have come to expect while maintaining flexibility to make it your own.

## Dependencies

React Devise has deep dependencies on some popular React modules. The most significant are:

* [react-redux](https://github.com/reactjs/react-redux)
* [react-router](https://github.com/ReactTraining/react-router)
* [redux-form](https://github.com/erikras/redux-form)

If you don't want to use these in your app, then React Devise is probably not for you.

## Installation

```
yarn add react-devise
```

## Basic Usage

Call ```initReactDevise``` as early as possible in your application, and before using any other part of React Devise. This function returns a function which returns the config object.

Add ```reactDeviseReducers``` to your store.

Within the ```Router``` element place ```AuthRoutes```. Set the path to ```clientResourceName``` to cause the router to select among the full auth routes.

Use ```PrivateRoute``` for any route that requires authorization. If the user visits a private route while not authenticated, he will be redirected to the login route.

```javascript
import {createStore, combineReducers} from 'redux';
import {Provider} from 'react-redux';
import {Router, Route, Switch} from 'react-router-dom';
import createBrowserHistory from 'history/createBrowserHistory';
import reducers from './reducers'
import {initReactDevise, AuthRoutes, PrivateRoute} from 'react-devise';
import reactDeviseReducers from 'react-devise/lib/reducers';

const {clientResourceName} = initReactDevise()();

const store = createStore(
  combineReducers({
    ...reducers,
    ...reactDeviseReducers
  })
);

const App = () => {
  return (
    <Provider store={store}/>
      <Router history={createBrowserHistory()}>
        <Switch>
          <PrivateRoute exact path="/products" component={Products} />
          <Route exact path="/" component={Home} />
          <AuthRoutes path={`/${clientResourceName}`} />
          <Route component={NotFound} />
        </Switch>
      </Router>
    </Provider>
  );
}
```

Given that the default value of ```clientResourceName``` is "users" the default auth routes are:

* /users/login
* /users/sign-up/
* /users/confirmation/new
* /users/confirmation
* /users/password/new
* /users/password/edit

## Customization

To customize the appearance and behavior of React Devise, pass a settings object into ```initReactDevise```.

```javascript
import ReactDeviseMaterialUI from 'react-devise-material-ui';
import {Form, Alert, UnstyledList, UnstyledListItem, FormError, AuthHeading, AuthViewContainer} from '../components';

const myCustomPlugin = {
  Form,
  FormError,
  Alert,
  AuthLinksList: UnstyledList,
  AuthLinksListItem: UnstyledListItem,
  Heading: AuthHeading,
  View: AuthViewContainer
};

initReactDevise({
  clientResourceName: 'customers',
  apiResourceName: 'api/auth',
  apiHost: 'http://auth.example.com',
  viewPlugins: [
    ReactDeviseMaterialUI.plugin(),
    myCustomPlugin
  ],
  messages: {
    loginFailed: 'Whoa there. Bad login!'
  }
});
```

| Setting                     | Default Value | Description                                                                              |
| ----------------------------|---------------|------------------------------------------------------------------------------------------|
| `clientResourceName`        | "users"       | The first node in the route to each auth view. |
| `apiResourceName`           | "auth"        | The resource name used by Devise on the server. The first node in the path of API calls. |
| `apiHost`                   | `undefined`   | Omit unless your devise API is host on a different domain than the website. |
| `viewPlugins`               | []            | Use one or more view plugins to inject custom components into React Devise views.<br><br>View plugins are merged in order *after* the default plugin. Taking the code sample above, ```myCustomPlugin``` supersede ```ReactDeviseMaterialUI``` plugin, which in turn supersede the default plugin. |
| `defaultViewPluginSettings` | {}            | To customize the default plugin, provide a settings object. This can be used in conjunction with custom plugins or without them.<br><br>To see the available settings, find the default plugin [here]( https://github.com/timscott/react-devise/blob/master/src/config/viewPluginPlain.js). |
| `messages`                  | {}            | Override the default messages used by React Devise. Default messages are [here](https://github.com/timscott/react-devise/blob/master/src/config/defaultMessages.js). |


### Styled Components

React Devise plays nicely with [styled-components](https://github.com/styled-components/styled-components). For example, ```UnstlyedList``` (in the prior code sample) might be:

```javascript
import styled from 'styled-components';

const UnstyledList = styled.ul`
  list-style: none;
  padding: 0;
  a {
    text-decoration: none;
  }
  a:hover {
    text-decoration: underline;
  }
`;

export default UnstyledList;
```

## Accessing Configuration in Your Components

You can access React Devise config and also the ```AuthLinks``` component in your components by using ```withAuth``` like so:

```javascript
import React from 'react';
import {connect} from 'react-redux';
import {withAuth} from 'react-devise';

const Home = ({currentUser, auth: {AuthLinks}}) => {
  return (
    <div>
      <div>
        <h2>Welcome {currentUser && currentUser.email}</h2>
        <AuthLinks />
      </div>
    </div>
  );
};

const mapStateToProps = state => {
  return {
    currentUser: state.currentUser
  };
};

export default connect(mapStateToProps)(withAuth(Home));
```

## Middleware for Clients

Clients like [Apollo](https://github.com/apollographql/apollo) handle data access for you. React Devise provides middleware to add authentication to all requests. For example:

```javascript
import ApolloClient, {createNetworkInterface} from 'apollo-client';
import {addAuthorizationHeaderToRequest} from 'react-devise';

const networkInterface = createNetworkInterface({
  uri: '/graphql'
});

networkInterface.use([{
  applyMiddleware: addAuthorizationHeaderToRequest
}]);
```

```addAuthorizationHeaderToRequest``` takes a ```request``` and an optional ```next``` callback.

## Devise Server Setup

Eventually we want to create a gem to avoid repeating this boilerplate. For now, you have to set it up yourself.

First, we'll use the [devise-jwt](https://github.com/waiting-for-dev/devise-jwt) gem.

```ruby
# Gemfile

 gem 'devise-jwt'
```

Add the ```devise-jwt``` bits in the user model (or whatever your authenticated resource is).

```ruby
# /app/models/user.rb

class User < ApplicationRecord
  devise :database_authenticatable, :registerable, :recoverable, :trackable, :validatable, :confirmable,
    :jwt_authenticatable, jwt_revocation_strategy: Devise::JWT::RevocationStrategies::Null

  def jwt_payload
    {
      user_id: id,
      email: email,
      firstName: first_name,
      lastName: last_name
    }
  end

end
```

Next, we might need to set a custom path in our routes to match the ```apiResourceName``` if it's not the same as your user model name.

```ruby
# routes.rb

devise_for :users, path: :auth
```

Next, we need to change auth failure behavior:

```ruby
# /app/controllers/custom_auth_failure.rb

class CustomAuthFailure < Devise::FailureApp

  def respond
    self.status = :unauthorized
{'fraction_non_alphanumeric': 0.12358070384543002, 'fraction_numerical': 0.0003763879304936955, 'mean_word_length': 3.5574614065180103, 'pattern_counts': {'":': 0, '<': 48, '<?xml version=': 0, '>': 51, 'https://': 15, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1287251', 'n_tokens_mistral': 4451, 'n_tokens_neox': 4345, 'n_words': 1630}