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:
- Create a
logUserIn()
method which will accept user input and call yourfetchToken()
method. You may need to adjust that depending on how it was previously working. - Store the token somewhere.
- Handle an expired token in our
performRequest
method. - 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');