Chapter 3: User Accounts
ALANA is a platform for many things, but none of those things are possible without people. As with any web application, our people are our users, they have accounts, and it's through these that they build up a profile of who they really are.
The active user
Most platforms need to deal with authentication: logging in and out, changing passwords, and so on. The ALANA SDK handles most of the dirty work for you, and it even hides a lot of complexity behind the idea of the active user.
Internally, when you log in, the ALANA Platform gives you a set of authentication tokens, which is standard practice. You, as a user, then include one of these tokens, the access token, as a header in future API calls to show who you are. Again, this is pretty standard across the internet.
The SDK encapsulates the logic of dealing with tokens almost completely. Instead of manually messing with request headers, you just call SDK methods as usual. If they need an access token, it'll be added. If that token needs to be refreshed, the SDK will handle that the next time you try to do something that requires it.
All this is covered by the concept of the active user. Each instance of the SDK has an active user, which is the last user who successfully logged in using the Auth.login() method. Until you call Auth.logout(), the active user will remain the same.
Multiple SDK instances
Yes, you can create multiple instances of the SDK and use them independently. You do have to track them manually, but it may be useful for features such as roaming profiles.
Logging in
Naturally, the first thing a user will want to do is log in. Auth.login() does the job there. Let's create a separate login page, call it login.html. This page will be just a simple form that calls a new function, doLogin() instead of submitting to the server.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Alana Platform SDK test</title>
<script type="module">
import { doLogin } from "/site.js";
document.querySelector("#login-form").addEventListener("submit", doLogin);
</script>
<style>
.hidden { display: none; }
</style>
</head>
<body>
<header>
<h1>Welcome to Inner Light Wellness</h1>
</header>
<main>
<h2>Log in to access your account and members-only rewards!</h2>
<form id="login-form" method="get" action="">
<div class="form-row">
<label for="login-form-email">Email address</label>
<input type="text" name="email" id="login-form-email" required />
</div>
<div class="form-row">
<label for="login-form-password">Password</label>
<input type="password" name="password" id="login-form-password" required />
</div>
<div class="form-row form-buttons">
<button type="submit" id="login-form-submit">Log In</button>
</div>
<div class="form-row-error hidden" id="login-form-error">
Incorrect email or password. Please try again.
</div>
</form>
</main>
<footer>
<p>Powered by <a href="https://alana4.com">ALANA4 and the ALANA SDK</a></p>
</footer>
</body>
</html>import Alana from "https://sdk.alana4.com/js/alana.js";
const API_KEY = "911e5ddf75e11f976c6ed21df4c24834a92f65b0ce38484c7a58304b2a353aa0";
const API_USERNAME = "innerlightdemo";
const Api = Alana({
apiKey: API_KEY,
apiUsername: API_USERNAME,
mode: 'development',
});
////
// Removed for clarity: showMemberships, listExperiences, getExperience
////
export async function doLogin(event) {
const form = document.querySelector("#login-form");
// We don't want to use the "real" form action in this tutorial.
event.preventDefault();
try {
const formData = new FormData(form);
const accessTokens = await Api.Auth.login(formData.get("email"), formData.get("password"));
// In a real app, we would probably have a state manager like Redux or Pinia.
localStorage.setItem("access_tokens", JSON.stringify(accessTokens));
// Redirect to the home page, now logged in.
location.href = "/";
} catch (e) {
document.querySelector("#login-form-error").classList.toggle("hidden");
}
}
Creating accounts
Well, they'll probably want to create accounts first, but this is assuming they've already done that. You can use your own email and password for these examples.
Our doLogin() function is a quick and dirty one, and the only real complexity comes from our lack of state management. Obviously, the real thing would have much better error checking, password validation, etc.
On line 24, we call the Auth.login() SDK method, passing the email address and password of the user who is trying to log in. In return, assuming the password was correct, we get back a set of tokens in an object.
As the comment says, line 27 is where a real app would do state handling. For this tutorial, we just stuff the tokens into the browser's local storage. Remember that localStorage items are always strings; that's why we have the call to JSON.stringify().
Following this is a simple redirect back to the home page. If anything goes wrong, the user gets an error message instead. We could clear the form or do any other handling here, too.
Back at home
We have an active user, and we've just sent that user back to the home page, so the next logical step is to add something to that home page. We'll put in a simple welcome message and, more importantly, a button to log out.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Alana Platform SDK test</title>
<script type="module">
import { showMemberships, getCurrentUser, useAccessTokens, doLogout } from "/site.js";
await showMemberships();
useAccessTokens();
const user = await getActiveUser();
if (user) {
const header = document.querySelector("#logged-in-header");
header.classList.toggle("hidden");
header.querySelector("#active-user-email").textContent = user.email;
}
document.querySelector("#logout-link").addEventListener("click", doLogout);
</script>
<style>
.hidden { display: none; }
</style>
</head>
<body>
<header>
<h1>Welcome to Inner Light Wellness</h1>
<div id="logged-in-header" class="hidden">
<p>Welcome, <strong id="active-user-email"></strong>! <a href="/" id="logout-link">Logout</a></p>
</div>
</header>
<main>
<div id="memberships-list">
</div>
</main>
<footer>
<p>Powered by <a href="https://alana4.com">ALANA4 and the ALANA SDK</a></p>
</footer>
<template id="membership-card">
<section>
<h1 class="membership-card-title"></h1>
<p class="membership-card-description"></p>
<ul class="membership-card-features"></ul>
<div>
<span class="membership-card-price"></span>
<button type="button" class="membership-card-buy">Get This Plan</button>
</div>
</section>
</template>
</body>
</html>////
// Other code removed for readability
////
export async function doLogout(event) {
await Api.Auth.logout()
localStorage.removeItem("access_tokens");
location.reload();
}
export async function getActiveUser() {
if (localStorage.getItem("access_tokens")) {
return await Api.Profile.get()
} else {
return null;
}
}
export function useAccessTokens() {
const tokens = localStorage.getItem("access_tokens");
if (tokens) {
Api.setTokens(JSON.parse(tokens));
}
}
This is a bigger chunk of new code, and the logic might not be obvious, so let's take it slow.
First, we've made a helper function, useAccessTokens() to tell the SDK that it should use the tokens we stored earlier (when the user logged in) for any of its method calls. Front-end frameworks typically take care of this for us, but vanilla JS doesn't.
Next, another helper function, getActiveUser(), retrieves the profile of the active user. This only happens if we're sure there's an active user in the first place; that is, if we find the access token in local storage. Otherwise, the helper function returns null, and we continue as before.
It's what happens when we do have an active user that's interesting here. Starting on line 17 of index.html, we show the (normally hidden) banner greeting the user, and we plug in the user's email address to prove it's personalized. Then, we add an event handler to the "Logout" button that calls doLogout(). This final new function simply hands off to the SDK's Auth.logout() method, then clears the tokens and refreshes the home page.
The Profile object
Whew. That's actually a lot of functionality covered in a relatively short amount of code, but one specific bit deserves further mention. Going back to the getActiveUser() function, we see that it calls the get() method of the SDK's Profile resource.
This is the active user's profile. Here, we only wanted the email address from it (which is admittedly pretty silly, since the user just gave us that info when logging in), but the profile object has a lot more than that. See the User docs for the complete list, but many of these are helpful in a wide variety of situations:
firstNameandlastName, for the personal touchavatarUrl, to show the user's profile pictureaddress,city,state, andpostalCode, which will be useful when working with location resourcesrewardsBalance,credits, andcoins, the basis of the ALANA Economy
If that weren't enough, the Profile resource itself has more methods than just get(). Profile.notifications() lets you get a list of notification messages the user has received on the ALANA Platform. If the user has created a brand within your site, then Profile.brand() is how you can access it. The docs have a complete list of these methods.
Moving forward
Now that Bianca's users can log in, we can go back and fix the hole we left earlier. In the next chapter of the tutorial, we'll see how Inner Light's users can become members, with all the perks that entails.