60 min
Saito
  1. 1. Information
    1. 1.1. Ingredients
    2. 1.2. Functions
    3. 1.3. Requirements
      1. 1.3.1. Database
  2. 2. Create the application
    1. 2.1. Frontend
    2. 2.2. Backend
  3. 3. Customize the application
    1. 3.1. Frontend
    2. 3.2. Backend
  4. 4. Deploy the application
    1. 4.1. Backend
    2. 4.2. Frontend
  5. 5. Download
  6. 6. Version

It is really tedious to implement a function like a login-logout. We want to let someone handles it, aren’t we?

In this tutorial, I’ll show you how to build a chat application with Auth0 authentication and how to secure APIs (Only login-user can create rooms and post messages) by using Auth0.

Information

Ingredients

  • Template: Chat
  • API Loigcs: MongoDB CRUD, Auth0 Validate JWT

Functions

  • Providing authentication by using Auth0.
  • Providing secured APIs.
  • Storing chat data in Firebase Realtime Database.
  • Loading chat data from Firebase Realtime Database.

Requirements

Database

To run this program, you need to create MongoDB collections.

  • collections
    • sample_message
    • sample_stream

Create the application

Frontend

  1. Choose the Chat Template.

  2. Customize the UI in the K5 Playground.

Backend

  1. Edit the API Logic of POST /sample_streams and POST /sample_messages.

    1. Click the endpoints.

    2. Find Auth0 Validate JWT API Logic from Authentication menu at the right pane.

    3. Add it to the top of logic sequence.

    4. Modify the code as follows.

      1. Set credentials

        1
        2
        3
        4
        5
        var checkJWT = jwt({
        secret: '[API Signing Secret]',
        audience: '[API Identifier]',
        issuer: '[Domain]'
        });
  2. Download the application.

Customize the application

Frontend

  1. Install auth0-js module.

    1
    npm install --save auth0-js
  2. Add authentication handlers.
    Note: You can copy the files from Sample Project of Auth0 site. In that case, extract src/Auth folder and put it to app/utils.

    1. Create a config file named app/utils/Auth/auth0-variables.js

      1
      2
      3
      4
      5
      6
      export const AUTH_CONFIG = {
      domain: '[Domain]',
      clientId: '[Client ID]',
      callbackUrl: '[Callback URL]',
      audience: '[API Identifier]',
      };
    2. Create auth0 module named app/utils/Auth/Auth.js

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      import auth0 from 'auth0-js';
      import { AUTH_CONFIG } from './auth0-variables';

      export default class Auth {
      auth0 = new auth0.WebAuth({
      domain: AUTH_CONFIG.domain,
      clientID: AUTH_CONFIG.clientId,
      redirectUri: AUTH_CONFIG.callbackUrl,
      audience: AUTH_CONFIG.audience,
      responseType: 'token id_token',
      });

      constructor() {
      this.login = this.login.bind(this);
      this.logout = this.logout.bind(this);
      this.handleAuthentication = this.handleAuthentication.bind(this);
      this.isAuthenticated = this.isAuthenticated.bind(this);
      }

      login() {
      this.auth0.authorize();
      }

      handleAuthentication() {
      // Etract authentication result from URL
      const authResult = location.hash.substr(1).split('&').reduce(
      (authObject, hash) => Object.assign(
      {},
      authObject,
      { [hash.split('=')[0]]: hash.split('=')[1] },
      ),
      {},
      );

      if (authResult && authResult.access_token && authResult.id_token) {
      this.setSession(authResult);
      location.href = '/';
      } else if (authResult.error) {
      console.log(authResult.error_description);
      alert(`Error: ${authResult.error}. Check the console for further details.`);
      location.href = '/';
      }
      }

      setSession(authResult) {
      // Set the time that the access token will expire at
      const expiresAt = JSON.stringify((authResult.expires_in * 1000) + new Date().getTime());
      localStorage.setItem('access_token', authResult.access_token);
      localStorage.setItem('id_token', authResult.id_token);
      localStorage.setItem('expires_at', expiresAt);
      // navigate to the home route
      location.href = '/';
      }

      logout() {
      // Clear access token and ID token from local storage
      localStorage.removeItem('access_token');
      localStorage.removeItem('id_token');
      localStorage.removeItem('expires_at');
      // navigate to the home route
      location.href = '/';
      }

      isAuthenticated() {
      // Check whether the current time is past the
      // access token's expiry time
      const expiresAt = JSON.parse(localStorage.getItem('expires_at'));
      return new Date().getTime() < expiresAt;
      }
      }
  3. Add LOGIN/LOGOUT button to the under of ADD NEW STREAM button.

    1. Edit app/components/StreamList/StreamList.js.

      1. Import ‘app/utils/Auth/Auth.js’.

        1
        2
        3
        4
        5
        6
        import { RaisedButton002 } from 'material-ui-fj/Button';
        import Auth from '../../utils/Auth/Auth';

        const auth = new Auth();

        export default class StreamList extends Component {
      2. Add a Login/Logout Button.

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
          />
        <RaisedButton002
        label="Add New Stream"
        onClick={this.handleCreateStream}
        tabIndex={0}
        />
        <RaisedButton002
        label={auth.isAuthenticated() ? 'Logout' : 'Login'}
        onClick={this.handleAuthentication}
        style={{ marginTop: '2px' }}
        tabIndex={0}
        />
        </VerticalLayout>
      3. Create a handler for Logint/Logout.

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        }

        handleAuthentication() {
        if (auth.isAuthenticated()) {
        auth.logout();
        } else {
        auth.login();
        }
        }

        render() {
      4. Add componentWillReceiveProps to handle access_token.

        1
        2
        3
        4
        5
        6
        7
        8
        9
        }

        componentWillReceiveProps(nextProps) {
        if (/access_token|id_token|error/.test(nextProps.location.hash)) {
        auth.handleAuthentication();
        }
        }

        componentWillUnmount() {
  4. Disable POST button and the text fields while logout.

    1. Edit app/components/StreamContent/StreamContent.js.

      1. Add disabled props to the text fields and the button.

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
          <input
        type="text"
        ref={(component) => { this.userId = component; }}
        placeholder={'Name'}
        style={styles.userId}
        tabIndex={0}
        disabled={!auth.isAuthenticated()}
        />
        <textarea
        ref={(component) => { this.message = component; }}
        placeholder={'Message'}
        style={styles.message}
        tabIndex={0}
        onKeyDown={
        event => this.handleEnterMessage(event, currentStream._id, replyMessageId)
        }
        disabled={!auth.isAuthenticated()}
        />
        </VerticalLayout>
        <VerticalLayout style={styles.entrySubmit}>
        <BorderedFlatButton002
        label="post"
        onClick={() => this.handleSubmitMessage(currentStream._id, replyMessageId)}
        tabIndex={0}
        disabled={!auth.isAuthenticated()}
        />
  5. Modify request headers for post.

    1. Edit app/actions/StreamActionCreators.js

      1
      2
      3
      4
      5
      6
      createStream({ name, resolve }) {
      api.post('/sample_streams', { name }, {
      headers: {
      Authorization: localStorage.access_token ? `Bearer ${localStorage.access_token}` : undefined,
      },
      }).then((response) => {
    2. Edit app/actions/MessageActionCreators.js

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      sendMessage({ streamId, userId, message, replyMessageId, resolve }) {
      api.post('/sample_messages', {
      stream_id: streamId,
      user_id: userId,
      message,
      replyMessage_id: replyMessageId,
      }, {
      headers: {
      Authorization: localStorage.access_token ? `Bearer ${localStorage.access_token}` : undefined,
      },
      }).then(() => {

Backend

Nothing to be edited.

Deploy the application

Finally, all you need to do is deploying backend and frontend.

Backend

1
2
cd backend
cf push [APP_NAME]

Frontend

1
2
3
4
5
6
cd frontend
npm install
set API_URL=[Backend URL] # for Windows
export API_URL=[Backend URL] # for Mac/Linux
npm run build
cf push [APP_NAME] -p public

Download

Version

  • Chat Template: v1.0.0