Skip to content

Latest commit

 

History

History
292 lines (221 loc) · 7.54 KB

authentication.md

File metadata and controls

292 lines (221 loc) · 7.54 KB

Authentication with Logux

In this example, we will use simple email/password authentication. Logux supports any authentication, including OAuth or WebAuthn.

We will show two of the most popular cases:

  1. You already have Ruby/PHP/Python back-end server and HTML page with email and password fields. When user passed authentication back-end server redirects them and insert user ID and token as <meta> tags.
  2. You keep all your business logic in Logux Server and use HTTP just to send static HTML. In this case, Logux client connects as a guest and sends authentication action. Logux server sends action with a user ID and token back to the client. The client will save them to localStorage.

If you need another way, you can combine methods.

Method 1: HTML Form and Meta Tags

Method 1: Server

Go to your back-end server.

Ruby on Rails

Add jwt to Gemfile and call bundle:

gem 'jwt'

Add JWT secret key to local .env:

  LOGUX_CONTROL_SECRET=secret
  LOGUX_URL=http://localhost:31338
+ JWT_SECRET=secret

Edit config/initializers/logux.rb:

  config.auth_rule = lambda do |user_id, token|
-   # Allow only local users until we will have a proper authentication
-   Rails.env.development?
+   data = JWT.decode token, ENV['JWT_SECRET'], { algorithm: 'HS256' }
+   data[0]['sub'] == user_id
  end

Add <meta> tags to application layout used for authenticated user:

    meta( name="userId" content=current_user.id )
    meta( name="token" content=JWT.encode({ sub: current_user.id }, ENV['JWT_SECRET'], 'HS256') )
Any other HTTP server

Add JWT secret key to proper storage for your environment. Local .env is a good option.

  LOGUX_CONTROL_SECRET=secret
  LOGUX_URL=http://localhost:31338
+ JWT_SECRET=secret

Add library to support JWT. Add code to check userId and token from Logux:

data = JWT.decode(token, ENV['JWT_SECRET'])
return data.sub == userId

Generate token to use in HTML template:

token = JWT.encode({ sub: userId }, ENV['JWT_SECRET'])

Add this token and user ID to HTML templates used for authenticated user:

    <meta name="userId" content=<?= userId ?>>
    <meta name="token" content=<?= token ?>>

Method 1: Client

Use these <meta> values in the store:

Redux client
+ let userId = document.querySelector('meta[name=userId]')
+ let token = document.querySelector('meta[name=token]')

+ if (!userId) {
+   location.href = process.env.NODE_ENV === 'development'
+     ? 'http://localhost:3000/login'
+     : 'https://example.com/login'
+ }

  const client = new CrossTabClient({
    subprotocol: '1.0.0',
    server: process.env.NODE_ENV === 'development'
      ? 'ws://localhost:31337'
      : 'wss://logux.example.com',
-   userId: 'anonymous', // TODO: We will fill it in next chapter
-   token: '' // TODO: We will fill it in next chapter
+   userId: userId.content,
+   token: token.content
  })
Vuex client
+ let userId = document.querySelector('meta[name=userId]')
+ let token = document.querySelector('meta[name=token]')

+ if (!userId) {
+   location.href = process.env.NODE_ENV === 'development'
+     ? 'http://localhost:3000/login'
+     : 'https://example.com/login'
+ }

  const client = new CrossTabClient({
    subprotocol: '1.0.0',
    server: process.env.NODE_ENV === 'development'
      ? 'ws://localhost:31337'
      : 'wss://logux.example.com',
-   userId: 'anonymous',
-   token: ''
+   userId: userId.content,
+   token: token.content
  })

Method 1: Check the Result

Start back-end server, Logux proxy, and Logux client. Try to sign-in into application.

Method 2: Everything in Logux

Method 2: Server

Go to Logux Server and add the library to generate JWT:

npm i jwt-simple bcrypt

Load it in the index.js:

  const { Server } = require('@logux/server')
+ const bcrypt = require('bcrypt')
+ const jwt = require('jwt-simple')
  const pg = require('pg-promise')

Add JWT secret key to local .env config file:

  DATABASE_URL=postgres://localhost/server-logux
+ JWT_SECRET=secret

Go back to index.js and replace server.auth(…) with this code:

server.auth(({ userId, token }) => {
  if (userId === 'anonymous') {
    return true
  } else {
    try {
      const data = jwt.decode(token, process.env.JWT_SECRET)
      return data.sub === userId
    } catch (e) {
      return false
    }
  }
})

server.type('login', {
  async access (ctx) {
    return ctx.userId === 'anonymous'
  },
  async process (ctx, action, meta) {
    const user = await db.oneOrNone('SELECT * FROM users WHERE email = $1', action.email)
    if (!user) {
      server.undo(action, meta, 'Unknown email')
    } else if (await bcrypt.compare(action.password, hash)) {
      let token = jwt.encode({ sub: user.id }, process.env.JWT_SECRET)
      ctx.sendBack({ type: 'login/done', userId: user.id, token })
    } else {
      server.undo(action, meta, 'Wrong password')
    }
  }
})

Method 2: Client

In this example, we will implement sign-in outside of the application’s store, because guests don't need it. You can implement it the way you want, according to your design.

Sign-in user with simple Logux Client, save userId and token to localStorage and redirect to application:

import { Client } from '@logux/client'

function login (email, password) {
  let client = new Client({
    subprotocol: '1.0.0',
    server: process.env.NODE_ENV === 'development'
      ? 'ws://localhost:31337'
      : 'wss://logux.example.com',
    userId: 'anonymous'
  })
  client.type('login/done', action => {
    localStorage.setItem('userId', action.userId)
    localStorage.setItem('token', action.token)
    location.href = process.env.NODE_ENV === 'development'
      ? 'http://localhost:3000/dashboard'
      : 'https://app.example.com/dashboard'
  })
  client.type('logux/undo', action => {
    alert(action.reason)
  })
  client.start()
  client.log.add({ type: 'login', email, password }, { sync: true })
})

Use these localStorage values in the store:

Redux client
+ if (!localStorage.getItem('userId')) {
+   location.href = process.env.NODE_ENV === 'development'
+     ? 'http://localhost:3000/login'
+     : 'https://example.com/login'
+ }

  const client = new CrossTabClient({
    subprotocol: '1.0.0',
    server: process.env.NODE_ENV === 'development'
      ? 'ws://localhost:31337'
      : 'wss://logux.example.com',
-   userId: 'anonymous', // TODO: We will fill it in next chapter
-   token: '' // TODO: We will fill it in next chapter
+   userId: localStorage.getItem('userId'),
+   token: localStorage.getItem('token')
  })
Vuex client
+ if (!localStorage.getItem('userId')) {
+   location.href = process.env.NODE_ENV === 'development'
+     ? 'http://localhost:3000/login'
+     : 'https://example.com/login'
+ }

  const client = new CrossTabClient({
    subprotocol: '1.0.0',
    server: process.env.NODE_ENV === 'development'
      ? 'ws://localhost:31337'
      : 'wss://logux.example.com',
-   userId: 'anonymous',
-   token: ''
+   userId: localStorage.getItem('userId'),
+   token: localStorage.getItem('token')
  })

Method 2: Check the Result

In the next steps, you will need a good sign-up form, email verification, and many other things for proper authentication. They highly depend on your application and out of this guide topic.