Links

4. User Authentication

Redirect the user to the Login page to get the Access token

First, we need to redirect the user to their PDA where they can enter their password to complete the authorization.
We will need to build the URL using our app ID, our callback URI and a URI in case of failure.
The URL will look similar to this:
https://<hat-url>/#/hatlogin?
name=<application-id>
&redirect=<redirect>
&fallback=<fallback>
  • hat-url – The PDA’s unique URL. eg. testing.hubat.net
  • application-id – Our application ID. eg. testhatapp
  • redirect – The callback URI for our application. eg. http://localhost:3000/authentication
  • fallback – The URI will be called on authorization failure
Lets create a configuration file app.config.js in the src/ folder:
// src/app.config.js
export const appConfig = {
applicationId: 'testhatapp',
namespace: 'testhatapp',
hatCluster: '.hubat.net',
hatApiVersion: 'v2.6',
secure: true,
};
  • applicationId – The application ID
  • namespace – The application's namespace
  • hatCluster – The HAT's cluster for the login. In this case we are using '.hubat.net' for testing purposes only. For production you'd use a real non-dev-sandbox PDA
  • hatApiVersion – The API version of the HAT
  • secure – If you want to run the HAT locally, you have to modify this field to 'false'
For better user experience, it's good practice to validate the user's input and navigate them to their PDA to Login only if their username is valid and exists. Otherwise we'll display an error message to inform the user about the issue. You can use the HAT-JS to check if a PDA with this username exists.
Let's create the redirectValidUser method to redirect an existing user. We can use the hat-js to check if the user exists:
// src/features/Login/LoginPage.js
import React, { useState } from 'react';
import { HatClient } from '@dataswift/hat-js';
import { isHatName } from "../../utils/validations";
import { appConfig } from "../../app.config";
function LoginPage() {
const [username, setUsername] = useState('');
const [errorMsg, setErrorMsg] = useState('');
const errorMessages = {
usernameNotValid: 'Sorry, this username is not valid',
usernameNotRecognised: 'Sorry, this username is not recognised'
};
const redirectValidUser = async (username) => {
try {
const hat = new HatClient({});
const hatDomain = username + appConfig.hatCluster;
// Use the hat-js's helper method to check if the user exists
const res = await hat.auth().isDomainRegistered(hatDomain);
// if the res is true, navigate the user to their PDA
if (res) {
const hatUrl = `https://${hatDomain}`;
const redirectUrl = `http://${window.location.host}/authentication`;
const fallback = `http://${window.location.host}/authentication`;
const applicationId = appConfig.applicationId;
window.location.href = hat.auth().generateHatLoginUrl(hatUrl, applicationId, redirectUrl, fallback);
} else {
// Display an error message when the username is not recognised
setErrorMsg(errorMessages.usernameNotRecognised);
}
} catch (e) {
console.log(e);
}
};
const validateLoginDetails = () => {
if (isHatName(username)) {
// Use the new method when the username is valid
redirectValidUser(username);
} else {
setErrorMsg(errorMessages.usernameNotValid);
}
};
return (
<form>
.
.
</form>
);
}
Upon successful login, the HAT will verify if the application has been granted all the permissions it needs. If not, the HAT user will need to provide these permissions for the app to access their HAT.
Lastly, the user will be redirected back to our application with the authentication token attached. This should be stored for subsequent API calls:
http://localhost:3000/authentication?token=eyJ0eXAiOiJKV1Qi….

Authenticate user

To collect the token parameter, we will need to create a new component in the Login feature

Create the authentication handler

In this example we will store the token value in the Session Storage. Upon successful login we will navigate the user back to the HomePage where we will need to add a different screen to display to the user when they are authenticated. In case of error, we will redirect the user back to the Login page.
// src/features/Login/AuthenticationHandler.js
import React, { useEffect } from 'react';
import { useHistory } from 'react-router';
import { getParameterByName } from "../../utils/windowHelper";
function AuthenticationHandler() {
const history = useHistory();
useEffect(() => {
const token = getParameterByName('token');
const error = getParameterByName('error');
if (error) {
history.push('/login');
}
if (token) {
sessionStorage.setItem('token', token);
history.push('/');
}
}, [history]);
return <div>Loading...</div>;
}
export default AuthenticationHandler;

Get parameter by name

In order to get the query parameter value, let's create a new function getParameterByName in the utils/ folder:
// src/utils/windowHelper.js
export const getParameterByName = variable => {
const query = window.location.search.substring(1);
const vars = query.split('&');
for (let i = 0; i < vars.length; i++) {
const pair = vars[i].split('=');
if (pair[0] === variable) {
return decodeURIComponent(pair[1]);
}
}
return null;
};
Import the function in the AuthenticationHandler.js:
// src/features/Login/AuthenticationHandler.js
import { getParameterByName } from "../../utils/windowHelper";
Include the AuthenticationHandler in the Routing:
// src/app/Routing.js
import AuthenticationHandler from "../features/Login/AuthenticationHandler";
function Routing() {
return (
<Router>
<Header />
<Switch>
.
.
<Route path="/authentication" component={AuthenticationHandler} />
</Switch>
</Router>
);
}