Logo credits to vuejs.org and golang.org

Social application with Vue.js and GO

Create and serve a twitter like application with vue.js and golang PART 9: Store authentication token with indexedDB

Ivano Dalmasso
Apr 14 · 5 min read

This is the ninth part of this serie. Check here all the parts:

In this lesson we’ll change the authentication setup on the client layer so that the access login and logout won’t be lost closing the browser. This will be a little short one, you can find the code, that will upgrade only the frontend, here.

Saving authentication data to the client

Now we have a problem here: our client did not store this token anywhere if not in the store, and this is being cleared each time the browser is refreshed.

To solve this we are going to store the authentication data in indexedDB, a special store that all modern browser can use to save some data (i.e. return values from API).

Now, please avoid thinking about using local storage for saving this info, this is not secure at all, read this for understanding better why.

Actually there are a couple of tools that could be used to make this duty a little easier:

  • vuex-persist is a package that let’s you tell your store to use a personalized persistence layer, once setup everything will stay there
  • idb is a package that will make working with indexedDB easier and gives functions that returns promises, feature that indexedDB does not have natively

Actually I won’t use these two packages in this tutorial, we will instead go with pure indexedDB code.

Create a utility file to access indexedDB

Create a file /store/indexedDBUtils.js with the following code:

const DB_NAME = "authDB";
const DB_VERSION = 1;
let DB;
export default {
async getDb() {
return new Promise((resolve, reject) => {
if (DB) {
return resolve(DB);
}
let request = window.indexedDB.open(DB_NAME, DB_VERSION);
request.onerror = e => {
console.log("Error opening db", e);
reject("Error");
};
request.onsuccess = e => {
DB = e.target.result;
resolve(DB);
};
request.onupgradeneeded = e => {
console.log("onupgradeneeded");
let db = e.target.result;
let objectStore = db.createObjectStore("userAuth", {
autoIncrement: true,
keyPath: "username"
});
};
});
},
async addUser(user) {
//I ONLY want to have 1 user in this db... I delete all other before
let db = await this.getDb();
return new Promise(resolve => {
let trans = db.transaction(["userAuth"], "readwrite");
trans.oncomplete = () => {
resolve();
};
let store = trans.objectStore("userAuth");
store.openCursor().onsuccess = e => {
let cursor = e.target.result;
if (cursor) {
if (cursor.value.username != user.username) {
store.delete(cursor.value.username);
}
cursor.continue();
}
};
store.put(user);
});
},
async removeUser(user) {
if (!user || !user.username) {
return;
}
let db = await this.getDb();
return new Promise(resolve => {
let trans = db.transaction(["userAuth"], "readwrite");
trans.oncomplete = () => {
resolve();
};
let store = trans.objectStore("userAuth");
store.delete(user.username);
});
},
async getUser() {
let db = await this.getDb();
return new Promise(resolve => {
let trans = db.transaction(["userAuth"], "readwrite");
trans.oncomplete = () => {
resolve(user);
};
let user = null;
let store = trans.objectStore("userAuth");
store.openCursor().onsuccess = e => {
let cursor = e.target.resul
if (!cursor) return;
console.log(cursor.value);
user = cursor.value;
};
});
}
};

The first lines are self explanatory, we define the name of the store in indexedDB where we will save our data. Then we exports the functions that will give the access to our 4 functionalities in a promise-like way: get the database instance, create a user, remove a user, and retrieve a user.

The getDb function returns a promise that open the database and then, if needed (with the event “onupgradeneeded”) it updates it adding the userAuth store, that is actually what you can call a “table” in a relational database. Note that the userAuth also gets as “keyPath” the name of the key of the objects to use. In this case, we will insert the user data using the username as key. This call sets the DB variable we have created before, so that it can be used as first call in each of the others functions.

AddUser is the first function where we can see actually how indexedDB works. It gets the database instance as first, then it creates a “transaction” on the database. The result of this transaction is what will decide the return status of the promise. As first thing, here, we get a objectStore instance of the store created before (“userAuth”), then we delete all the records that are inside it (this is because we only want to save the actual user in this database, remember that we are actually in the user browser). At the end we store the actual user data using the store.put() call.

RemoveUser is actually the same as addUser, it just removes the user from the database, while getUser actually creates a cursor and returns the first record found in the database (remember, there should be only one!).

Actually this is all we need to make this work, now we can work again in the store to update it and use this functionality.

Use the utility functions in the store

async login(context, { username, password }) {
....
.then(data => {
dbUtils.addUser({ username: username, token: data.token });
context.commit("LOGIN",
{
username: username, token: data.token
}
);
})
.catch(error => {
dbUtils.removeUser({ username: username });
context.commit("LOGOUT");
throw error;
});

The same update can be done in the signup action, while the logout action should only get the removeUser part.
Now after a user login or signup, the “user” object is automatically put in the store.

Create now a new action to retrieve the user when needed:

async loadUser(context) {
dbUtils.getUser().then(user => {
console.log(user);
if (user && user != {})
context.commit("LOGIN", user);
});
}

This actually just check if there is a user in the database. If the answer is positive, the store actually commit the login mutation with this user. Remember that the token is actually inside of the user object, so everything now is going to persist.

We now have to dispatch this action when needed.
The easiest and more immediate place where to do this is in the mounted method of the app itself, so just go in the ./App.vue file and add a mounted() function to the configuration object of our app like this:

mounted() {
this.$store.dispatch("auth/loadUser");
}

And this is all we have to do about this.

Now you can try to launch the application, and it will check in the database if there is a user. If it is then found, it will be stored in the store and its token sent to our server, like before.

In the next lesson we are going to add mongoDB to store the data on server side, so the application will be finally able to be called as “complete”.

Nerd For Tech

From Confusion to Clarification

Nerd For Tech

NFT is an Educational Media House. Our mission is to bring the invaluable knowledge and experiences of experts from all over the world to the novice. To know more about us, visit https://www.nerdfortech.org/.

Ivano Dalmasso

Written by

Always looking to learn new things, and loving see things work as I want

Nerd For Tech

NFT is an Educational Media House. Our mission is to bring the invaluable knowledge and experiences of experts from all over the world to the novice. To know more about us, visit https://www.nerdfortech.org/.