Skip to content

Authentication

Jakub T. Jankiewicz edited this page Jul 30, 2023 · 14 revisions

If you want to restrict the users that can use the terminal, you can use authentication. The way jQuery Terminal implements authentication is using a token. The token is a way to validate if the client and server are the same people. The token is any true value, but ideally, it should be a unique identifier that can be validated on the server. Example result of any hash function like md5 (I think you don't need to worry about collisions in the token, but you can use a more secured hash if you want) on current time saved in a safe place, like the database or a session.

Automatic login command

The simplest way of authentication is login option. If you have a JSON-RPC interpreter you can set this option to true or stings and it will call the login RPC method or method specified by the option and if RPC returns a truthy value, the best is token hash as string, it will log you in the terminal. And from now on each call to the RPC method will have a token as the first argument so each method on the server should check if the token is valid.

And since the token is just a string, you can use JWT as a token.

Examples

$('body').terminal('service.php', {
  login: true
});
$('body').terminal('service.php', {
  login: "auth"
});

and JSON-RPC service requires each procedure to have a token as the first argument and validate it. It should throw an error if the token is invalid. When this happens the command will show an error on the terminal. You can use any implementation of JSON-RPC in your server-side language of choice.

Login method

You can also specify a function as login, where you can use naive check for the password in JavaScript or call the server to verify the user (you don't need to use JSON-RPC but it requires more code), which is a proper way to do.

$(function() {
    $('#terminal').terminal({}, {
        login: function(user, password) {
            return fetch('auth.py', {
                method: 'POST',
                body: JSON.stringify({user, password}),
                headers:{
                    'Content-Type': 'application/json'
                }
            }).then(res => res.json());
        }
    });
});

and auth.py should read JSON as POST data to validate that this is a valid user and if so generate a token (any value) and save it in a safe place (that can't be accessed from the web).

Here we use fetch API to send POST request to the server in auth.py URI and whatever JSON it returns it will be used as a token, the server needs to return JSON string (texts "<generated token>") or null (false will also work). auth.py should read JSON as POST data to validate that this is a valid user and if so generate a token (any value) and save it in a safe place (that can't be accessed from the web).

Manual Login Command

If you want to have a prompt like in one of the previous sections (different prompt for login in users and guests) then you can't use the login option because this will not allow using the terminal without login. Instead, you need to create a command that will call the login method.

$(function() {
    $('#terminal').terminal({
        hello: function() {
            // client side only validation if token exists
            // to protect the app you should validate
            // the token on the server
            if (this.get_token()) {
               this.echo('Welcome');
            } else {
               this.error("You need to login first");
            }
        },
        login: function() {
            if (this.get_token()) {
                this.error("you're already logged in");
            } else {
                this.login(function(user, password, callback) {
                    // this is just example in real applications check need to be on the server
                    // and token should be unique value generated on the server that you can
                    // be checked if it's valid by server
                    if (user === 'john' && password === 'connor') {
                        callback("SKYNET");
                    } else {
                        callback(null);
                    }
                });
            }
        }
    }, {
        prompt: function(callback) {
            if (this.get_token()) {
                callback(this.login_name() + '@server$ ');
            } else {
                callback('guest@server$ ');
            }
        }
    });
});

Here is alternative version using a nested interpreter (push method) that you can exit from (with exit command or with CTRL+D):

  $('body').terminal(function(command, term) {
    var cmd = $.terminal.parse_command(command);
    if (cmd.name === 'login') {
      term.push({
        hello: function() {
          this.echo('hello there');
        }
      }, {
        prompt: 'obi> ',
        login: function(username, password) {
          if (username === 'obi' && password === 'one') {
            return Promise.resolve('TOKEN');
          }
          return Promise.reject();
        }
      });
    } else {
      this.error('Invalid Command.');
    }
  });

login method works the same as the login option. The callback is an old API that was not removed to not introduce breaking changes.

In every API that has a callback you can return a promise.

Autologin

There is also an option to autologin if you get the token from somewhere else. Like from the other tab when using cross tab communication sysend library.

sysend.on('login', ({ user, token }) => {
   term.autologin(user, token);
});

JWT

In version 2.37.0 with the introduction of the RPC interceptor (rpc option), there is a way to create seamless JWT (access and refresh tokens). The code for the example (both browser and example PHP service) is located in php-terminal-jwt repository.

Clone this wiki locally