DevCon, DevCon 2019, FileMaker, FileMaker Data API

FileMaker Data API Workshop – activity six

Logging users in

Now that we’ve got the unauthenticated components of the site done we need to work on getting users logged in. To do that we need to make some changes:

  1. Create a logUserIn() method which will accept user input and call your fetchToken() method. You may need to adjust that depending on how it was previously working.
  2. Store the token somewhere.
  3. Handle an expired token in our performRequest method.
  4. Make sure that when they log out their token is removed from storage
Logging in

If you take a look in assets/login.js You'll find there are a number of functions already there to help with logging in. Mostly they manage the user interface to update it when the user logs in and out, but the two which are going to be needed in this activity loginSuccess and loginError which can be used to handle a successful or unsuccessful login.

Storing the token

In JavaScript your best bet is to use sessionStorage. sessionStorage is perfect because it lasts for the life of the current browser session, and survives page reload, but once the browser is closed it's destroyed. This ensures that users get logged out when they close their browser.

You make use of sessionStorage like this

// Save data to sessionStorage
sessionStorage.setItem('key', 'value');

// Get saved data from sessionStorage
let data = sessionStorage.getItem('key');

// Remove saved data from sessionStorage
sessionStorage.removeItem('key');

// Remove all saved data from sessionStorage
sessionStorage.clear();
Logging in

A request to login begins in src/Controller/LoginController.php where the doLogin function receives the username and password which have been passed from the login form and calls the method $fm->logUserIn() the stub of which is in src/Service/FileMakerAPI.php.

Storing the user token

You could store the token to disk, but you'd somehow need to namespace that to the user, so it's going to be easier to utilise the user's session which takes care of that for us.

// stores an attribute in the session for later reuse
$this->session->set('attribute-name', 'attribute-value');

// gets an attribute by name
$foo = $this->session->get('foo');

// the second argument is the value returned when the attribute doesn't exist
$filters = $this->session->get('filters', []);

Solutions

An example of logUserIn. If you've used the fetchToken method from the solution to activity four then no modifications are required to that - it'll work 'out of the box' with the below doLogin.

logUserIn: function(username, password) {
    FileMaker.fetchToken(username, password, Login.loginSuccess, Login.loginError);
},

The performRequest method needs updating to attempt a re-login on token expiry. Locate

if([105, 952].includes(code) && !FileMaker.retried) {
    // TODO come up with some way to try logging the user back in
}

And replace it with

if([105, 952].includes(code) && !FileMaker.retried) {
    FileMaker.retried = true;
    FileMaker.clearToken();
    FileMaker.performRequest(method, urlSuffix, data, success, error);

    return;
}

In terms of using session storage for the token, replace the three token storage methods, retrieveToken, saveToken, and clearToken with the versions below.

Together clearToken and retrieveToken work together to ensure that an expired token is replaced. When performRequest needs a token it calls retrieveToken which will try to return the value stored in sessionStorage. If there's nothing there it will populate with fetchToken. clearToken ensures the the next request will result in a new token being fetched.

retrieveToken: function() {
    let token = sessionStorage.getItem('fmToken');
    if(!token) {
        this.fetchToken(Config.params.username, Config.params.password, App.error, App.error);
    }

    return sessionStorage.getItem('fmToken');
},

saveToken: function(token) {
    sessionStorage.setItem('fmToken', token);
},

clearToken: function() {
    sessionStorage.removeItem('fmToken');
    Login.doLogout();
}

An example of logUserIn. If you've used the fetchToken method from the solution to activity four then no modifications are required to that - it'll work 'out of the box' with the below doLogin.

logUserIn: function(username, password) {
    FileMaker.fetchToken(username, password, Login.loginSuccess, Login.loginError);
},

The preformRequest method needs updating to attempt a re-login on token expiry. Assuming your using something similar to the sample answer for activity four, when you first receive the response from fetch (almost certainly in a then() use something like: the below which will result in the next request loading a new token (see further below for clearToken.

if([401, 500].includes(response.status) && !FileMaker.retried) {
    FileMaker.retried = true;
    FileMaker.clearToken();

    return;
}

In terms of using session storage for the token, replace the three token storage methods, retrieveToken, saveToken, and clearToken with the versions below.

Together clearToken and retrieveToken work together to ensure that an expired token is replaced. When performRequest needs a token it calls retrieveToken which will try to return the value stored in sessionStorage. If there's nothing there it will populate with fetchToken. clearToken ensures the the next request will result in a new token being fetched.

retrieveToken() {
    let token = sessionStorage.getItem('fmToken');
    if(!token) {
        this.fetchToken(Config.params.username, Config.params.password, App.error, App.error);
    }

    return sessionStorage.getItem('fmToken');
},

saveToken: function(token) {
    sessionStorage.setItem('fmToken', token);
},

clearToken: function() {
    sessionStorage.removeItem('fmToken');
    Login.doLogout();
}

An example of logUserIn. If you've used the fetchToken method from the solution to activity five then no modifications are required to that - it'll work 'out of the box' with the below logUserIn.

/**
 * @param $username
 * @param $password
 *
 * @throws Exception
 */
public function logUserIn($username, $password)
{
    // If someone's trying to login then we need to pass their username and
    // password through to the 'fetchToken' method which expects these to come in
    // 'username' and 'password' in the array of params passed in
    $params = [
        'username' => $username,
        'password' => $password
    ];

    $this->fetchToken($params);
}

The preformRequest method needs updating to attempt a re-login on token expiry. Locate

if (in_array($content->messages[0]->code, [105, 952]) && !$this->retried) {
    // TODO come up with some way to try logging the user back in
}

And replace it with

if (in_array($content->messages[0]->code, [105, 952]) && !$this->retried) {
    $this->retried = true;
    $this->forceTokenRefresh();
    return $this->performRequest($method, $uri, $options);
}

In terms of using session storage for the token, replace the three token storage methods, retrieveToken, saveToken, and clearToken with the versions below.

Together clearToken and retrieveToken work together to ensure that an expired token is replaced. When performRequest needs a token it calls retrieveToken which will try to return the value stored in sessionStorage. If there's nothing there it will populate with fetchToken. clearToken ensures the the next request will result in a new token being fetched.

/**
 * @return string
 *
 * @throws Exception
 */
private function retrieveToken()
{
    $token = $this->session->get('fmToken');

    if ($token) {
        return $token;
    }

    $this->fetchToken($this->config);
    return $this->session->get('fmToken');
}

/**
 * @param string $token
 */
private function saveToken($token)
{
    $this->session->set('fmToken', $token);
}

/**
 *
 */
private function clearToken()
{
    $this->session->set('fmToken', false);
}

Finally to ensure that a user is logged out, in src/Controller/LoginController.php at line 46 replace the TODO with.

$session->remove('fmToken');

< Back to activity fiveOn to activity seven >

Leave A Comment

*
*