User Authentication. Web UI: login

Overview

This tutorial will show you how to implement a session login with a Preact-based user interface (UI) over a REST-based backend.

We'll concentrate here on the basics of the login process, for other details please check the rest of the tutorials in the Web UI section.

The login process is the following:

  • At page load, the JavaScript code will try to GET /api/login, which requires authorization
  • If it fails, it will present the login screen
  • Once you enter your credentials, it will GET /api/login again but this time using the authorization headers
  • If user and password are valid, the server will return a token
  • That token can be used for further accesses, until it expires or the user logs out

Build and try

-- Follow the Build Tools tutorial to setup your development environment.

  • Start a terminal in the project directory; if you've not already done so, clone the Mongoose Library repo

    git clone https://github.com/cesanta/mongoose
    
  • Build and run the example

    cd mongoose/examples/webui-login
    make clean all
    
  • Go to http://localhost:8000 in your browser; the JavaScript code loads and executes, but authorization will fail, so you should see a simple login page:

demo web page
  • Login, the JavaScript code on the web page gets some data from the device and renders the UI
demo web page

Backend implementation

  • Every time we get an HTTP request, we handle authorization by checking for a valid user and denying all /api/ URIs in case there is not. We have a valid user if our function getuser() was able to get any user information from the request
  • This function, getuser(), tries to get user credentials from the headers using the mg_http_creds() function. Then we search through a user list to validate them. If the user is logging in, we'll receive user and password; but if it is already logged in, we'll get a token. This code snippet shows a simple way of doing user authentication, valid for the purpose of this example:
  • When we get an HTTP request for the /api/login URI, as the code above has already validated the request has proper user credentials, we return user name and token.
  • When we get an HTTP request for the /api/data URI, we serve some device data to be shown on the UI. Note we use mg_http_reply() to build our responses and take advantage of the identifier m and, through the macro MG_ESC(), function mg_print_esc(), to print double-quoted JSON-escaped strings.

Frontend implementation

The Preact framework is an alternative to React with the same modern API and a smaller footprint, ideal to fit on small devices with limited room for flash memory.

  • Once the JavaScript code is loaded by the browser, it will call the onload method, which will invoke Preact's render() method to execute the function App() and fill the HTML body section of the document.
  • The App() function will keep the user name and some device data as state variables.
  • When first executed, it invokes Preact's useEffect() hook, which tries to check if we are already logged in. It does this by calling the fetch() method to GET /api/login with no authorization headers. If it succeeds, it will call the function login(), which we'll see later; if it fails, it will clear any user information.
  • If there is no user information, the returned HTML code will in fact include the execution of the function Login(), which gets passed the already mentioned login() function among its props.
  • The Login() function initializes user credentials as empty strings in its local state variables, and renders the login screen. Each generated HTML input is configured to call its corresponding update function, while the login button will call an internal login() function
  • When credentials are entered at the login screen, this internal login() function executes and calls the fetch() method to GET /api/login using proper authorization headers. It will then extract the returned JSON object containing the user information (name and token), and call the login() function which got passed with its props.
  • This function will store the user token, which will be utilized for all remaining API transactions, and call the user name state update function, what will queue App() to be rendered again. Then, it will call our function getin() to get any device information to show, by GETing the /api/data URI and storing its results.
  • Finally, App() returns HTML code including the execution of a function that renders the navigation bar (showing the user name and a logout button) and the main screen (showing any obtained device data)
  • A logout simply clears the token and the user state variable