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 8: Token based authentication

Ivano Dalmasso
Published in
11 min readMar 29, 2021

--

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

In this lesson we’ll add an authentication layer in both the client vue application and in the go server. As a plus, we will also add the endpoint to get a single user, that will be used for the “user” page in the vue page.

We will update both the frontend and the backend in this session, you can find the code here:

Manage a simple token-based authentication

Let’s start adding a file where we’ll manage the routes regarding authentication. Add a file “/endpoints/auth.go”. There we will insert all the logic for registration, login and logout operation for a user.

Generation and check of the passwords hash are going to be done using the standard library bcrypt (“golang.org/x/crypto/bcrypt”) while for the jwt token creation and check we are going to use dgrijalva/jwt-go (“github.com/dgrijalva/jwt-go”), so just import them.

Note that the creation of the token needs a server side secret to be used, so we just can create a function to take it from an environment variable, like this (in “endpoints/utils.go”):

func getSecret() string{
secret:=os.Getenv("ACCESS_SECRET")
if secret==""{
//That's surely a big secret this way...
secret="sdmalncnjsdsmf"
}
return secret
}

Obviously if the secret is empty, a real server should panic and throw an error in our real world, but this is ok for our case for now.

Now, in auth.go create a struct that will be used to pass the username/password data to the endpoints for creation of the user and for login:

type User struct{
Username string `json:"username"`
Password string `json:"password"`
}

Then create the two endpoints too (note that we also create an array of users as “in memory” database, for now) and also a method to create a new token:

var users map[string][]byte = make(map[string][]byte)
var idxUsers int =0

//getTokenUserPassword returns a jwt token for a user if the //password is ok
func getTokenUserPassword(w http.ResponseWriter, r *http.Request) {
var u User
err:=json.NewDecoder(r.Body).Decode(&u)
if err!=nil{
http.Error(w, "cannot decode username/password struct",http.StatusBadRequest)
return
}
//here I have a user!
//Now check if exists
passwordHash, found:= users[u.Username]
if !found{
http.Error(w, "Cannot find the username", http.StatusNotFound)
}
err=bcrypt.CompareHashAndPassword(passwordHash, []byte(u.Password))
if err!=nil{
return
}
token, err:=createToken(u.Username)
if err!=nil{
http.Error(w, "Cannot create token", http.StatusInternalServerError)
return
}
sendJSONResponse(w, struct {Token string `json:"token"`}{ token })
}

func createUser(w http.ResponseWriter, r *http.Request){
var u User
err := json.NewDecoder(r.Body).Decode(&u)
if err!=nil{
http.Error(w, "Cannot decode request", http.StatusBadRequest)
return
}
if _, found:= users[u.Username]; found{
http.Error(w,"User already exists", http.StatusBadRequest)
return
}
//If I'm here-> add user and return a token
value, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
users[u.Username]=value
token, err:=createToken(u.Username)
if err!=nil{
http.Error(w, "Cannot create token", http.StatusInternalServerError)
return
}
sendJSONResponse(w, struct {Token string `json:"token"`}{ token })
}
func createToken(username string) (string, error) {
var err error
//Creating Access Token
atClaims := jwt.MapClaims{}
atClaims["authorized"] = true
atClaims["username"] = username
atClaims["exp"] = time.Now().Add(time.Minute * 15).Unix()
at := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims)
secret:= getSecret()
token, err := at.SignedString([]byte(secret))
if err != nil {
return "", err
}
return token, nil
}

The “createToken” function gets a string username and create a token with some claims, for example that the token should be used by exactly that user, and the expiration is 15 minutes. Then the function returns a token created using the application secret that could be used for the authentication for 15 minutes (but we have still to implement this).

The other 2 functions are actually pretty similar: both decode the body of the request as a json object of the type we created before, then the createUser try to add it to the “database”, while the getTokenUserPassword actually checks if the user exists. Then, if all these checks return successfully, these functions returns a “JSONified” object with a newly created token.

These two functions are actually the handlers for the functions of login and registration for the site, so just add the two following lines in the AddRouterEndpoints function of the utils.go file for binding them to the actual routes:

r.HandleFunc("/api/auth/login",
getTokenUserPassword).Methods("POST") r.HandleFunc("/api/auth/create-user", createUser).Methods("POST")

We now have a way to actually registrate and login a user. This is actually useless until we block the access of some parts of the server side.

Let’s address this, adding these two methods to the utils.go file:

func checkTokenHandler(next http.HandlerFunc) http.HandlerFunc{
return func(w http.ResponseWriter, r *http.Request) {
header := r.Header.Get("Authorization")
bearerToken := strings.Split(header, " ")
if len(bearerToken)!=2{
http.Error(w, "Cannot read token", http.StatusBadRequest)
return
}
if bearerToken[0] != "Bearer"{
http.Error(w, "Error in authorization token. it needs to be in form of 'Bearer <token>'", http.StatusBadRequest)
return
}
token, ok :=checkToken(bearerToken[1]);
if !ok{
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
claims, ok := token.Claims.(jwt.MapClaims)
if ok && token.Valid {
username, ok := claims["username"].(string)
if !ok {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
//check if username actually exists
if _, ok := users[username]; !ok{
http.Error(w, "Unauthorized, user not exists", http.StatusUnauthorized)
return
}
//Set the username in the request, so I will use it in check after!
context.Set(r, "username", username)
}
next(w, r)
}
}

func checkToken (tokenString string) (*jwt.Token, bool) {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(getSecret()), nil
})
if err!=nil{
return nil, false
}
if _, ok := token.Claims.(jwt.Claims); !ok && !token.Valid {
return nil, false
}
return token, true
}

The checkToken function get as input a string token, and returns a jwt.Token object after having validated its default values.

The checkTokenHandler instead returns an actual http handler function that can be used “before” the real handler of a route. In this handler it gets the token from the “Authorization” header, it makes an easy check of the formation of this header (it have to be “Bearer <token>”). Then, it get the jwt.Token object from the checkToken function, and makes an additional control on the existence of the username in the token in the database. It then sets the username in the context of the request, so we could also use it in the real request, and calls the real http handler that we want to call that is being passed to the checkTokenHandler function.

Note that if any of the checks fail, the real route is not called, and an error is returned to the client. We now just have to decorate all the routes we want to “protect” with an authentication process with this method, to have some protection, in addRouterEndpoints:

func AddRouterEndpoints(r *mux.Router) *mux.Router {
...
r.HandleFunc("/api/posts",checkTokenHandler(addPost))
.Methods("POST")
r.HandleFunc("/api/posts/{POST_ID}",checkTokenHandler(deletePost))
.Methods("DELETE")
r.HandleFunc("/api/posts/{POST_ID}/comments",
checkTokenHandler(addComment)).Methods("POST")
...

These three api are actually protected with a token authentication now.

A user will have to send a token with a valid user with this api to actually, for example, add a post. A little problem is still here, if a user “A” send a post with author user “B”, the system still accepts it, so let’s make this little fix too, in the posts.go file, add the following function:

func isUsernameContextOk(username string, r *http.Request) bool {
usernameCtx, ok:=context.Get(r, "username").(string)
if !ok{
return false
}
if usernameCtx!=username{
return false
}
return true
}

This just get the username from the requests, and compares it to a username we want to check. We can call this function in the three functions “addPost”, “deletePost” and “addComment” just after the deserialization of the post (or the comment) to check if the user of the post is the actual one in the request, like this:

if !isUsernameContextOk(post.Username, r){
http.Error(w, "Cannot manage post for another user", http.StatusUnauthorized)
return
}

Regarding the authentication, one last thing that can be done, is to give a way to refresh the token if it is almost expired, by using the old one to get a new one:

func getTokenByToken(w http.ResponseWriter, r *http.Request){
//Here I already have the token checked... Just get the username from Request context
username, ok :=context.Get(r,"username").(string)
if !ok{
http.Error(w, "Cannot check username", http.StatusInternalServerError)
return
}
token, err:=createToken(username)
if err!=nil{
http.Error(w, "Cannot create token", http.StatusInternalServerError)
return
}
sendJSONResponse(w, struct {Token string}{ token })
}

and actually bind it in addRouterEndpoints

r.HandleFunc("/api/auth/token",checkTokenHandler(getTokenByToken))
.Methods("GET")

This is actually not a real world scenario, obviously. This in reality should be used with a two token authentication/authorization style, so one token with long expiration used for authentication that is used to get an authorization token with short expiration. This is only a demonstrative snippet of code about how to do it.

As last thing for this tutorial chapter, we can add an api endpoint protected by token authentication to get a user data to show in the Users page.

Just add a new file ‘/endpoints/users.go’ and set the code of the endpoint there:

func getUser(w http.ResponseWriter, r *http.Request) {
log.Println("getuser called")
vars := mux.Vars(r)
user, ok := vars["USERNAME"]
if !ok {
http.Error(w, "Cannot find username in request",http.StatusBadRequest)
return
}
if _, ok :=users[user]; ok{
sendJSONResponse(w,
struct{Username string `json:"username"`;
Description string `json:"description"` }{user , ""})
return
}
http.Error(w, "Cannot find user", http.StatusNotFound)
}

and use the decorating handlers we used before to check the authentication in the addRouterEnpoints function

r.HandleFunc("/api/users/{USERNAME}",
checkTokenHandler(getUser)).Methods("GET")

Now we have to update the client vue application to use these new functionalities.

Authentication in the client application

Let’s start updating the client application to use the authentication on the server. Start with editing the “store/authStore/index.js” file, removing the actual default username and adding in the state a new token empty string. Then we will have to manipulate these two states in the LOGIN and LOGOUT mutations like this:

LOGIN(state, { username, token }) {
state.user.loggedIn = true;
state.user.username = username;
state.user.token = token;
},
LOGOUT(state) {
state.user.loggedIn = false;
state.user.username = "";
state.user.token = "";
}

Then we just have to update the actual actions and just make sure to call the correct apis (note: the logout action does actually not invalidate the token in the server side, in fact we did not manage a “token state” in the server right by now, so it just remove the token from memory and cleanup the user in the client application)

async login(context, { username, password }) {
return fetch("http://localhost:3000/api/auth/login", {
method: "POST",
body: JSON.stringify({
username: username,
password: password })
})
.then(response => {
if (!response.ok) {
throw new Error("Cannot login!");
}
return response.json();
}).then(data => {
context.commit("LOGIN",
{ username: username, token: data.token });
}).catch(error => {
context.commit("LOGOUT");
throw error;
});
},
async logout(context) {
context.commit("LOGOUT");
},
async signup(context, { username, password }) {
return fetch("http://localhost:3000/api/auth/create-user", {
method: "POST",
body: JSON.stringify(
{ username: username, password: password })
}).then(response => {
if (!response.ok) {
throw new Error("Cannot signup!");
}
return response.json();
}).then(data => {
context.commit("LOGIN",
{ username: username, token: data.token });
}).catch(error => {
context.commit("LOGOUT");
error.read().then((data, done) => {
throw Error(data);
});
});
}

Just note that any action now calls the right API, and then if successfully and if the response is ok it calls the correct mutation with the data returned from the call, else an error is thrown (so the caller of the action can act accordingly).

Finally, add an additional getter to be used with other api in other stores:

getTokenHeader(state) {
return "Bearer " + state.user.token;
}

Using this getter the other stores will be able to set the correct token for the calls. Let’s just do this for the posts store: this is actually really simple, in the three api we have protected just update the headers parameter of the fetch like the following:

headers: {
"Content-Type": "application/json",
Authorization: context.rootGetters["auth/getTokenHeader"] },

Now let’s update the user store too, removing the initialization of the users array in the state and creating an action that can actually call the ADD_USER mutation

state: {
loadedUsers: []
},
.....
actions: {
async addUser(context, { username }) {
return fetch("http://localhost:3000/api/users/" + username, {
headers: {
Authorization: context.rootGetters["auth/getTokenHeader"]
}
})
.then(response => {
if (!response.ok) throw new Error("Cannot get user");
return response.json();
})
.then(data => {
context.commit("ADD_USER", data);
})
.catch(error => {
console.log(error);
throw error;
});
}

This is actually the same we have already done with the posts.

These updates will work all by themselves, almost with no updates on the components logic. Now let’s change the login page to actually be a login/signup working page. Update the views/Login.vue component like this:

<template>
<div class="content">
<base-card>
<form @submit.prevent>
<label for="username">Username</label>
<input id="username" type="text" v-model="username" />
<label for="password">Password</label>
<input id="password" type="password" v-model="password" />
<button @click="loginButtonClicked">{{ buttonString }}</button>
<p v-if="error">{{ error }}</p>
<a href="#" @click.prevent="toggleLogin">{{ textLoginString }}</a>
</form>
</base-card>
</div>
</template>

<script>
import { mapActions } from "vuex";
import BaseCard from "../components/UI/BaseCard.vue";
export default {
components: { BaseCard },
data() {
return {
loginSelected: true,
username: "",
password: "",
error: ""
};
},
methods: {
...mapActions({ login: "auth/login", signup: "auth/signup" }),
loginButtonClicked() {
if (this.loginSelected) {
this.login({ username: this.username, password: this.password })
.then(() => {
this.$router.push({ name: "Posts" });
})
.catch(error => {
this.error = error;
});
} else {
this.signup({ username: this.username, password: this.password })
.then(() => {
this.$router.push({ name: "Posts" });
})
.catch(error => {
this.error = error;
});
}
},
toggleLogin() {
this.loginSelected = !this.loginSelected;
}
},
computed: {
buttonString() {
if (this.loginSelected) {
return "LOGIN";
} else {
return "SIGNUP";
}
},
textLoginString() {
if (this.loginSelected) {
return "Signup instead";
} else {
return "Login instead";
}
}
}
};
</script>

The page will show a login or signup request, and we will store this selection in the “loginSelected” variable, set by the “toggleLogin” method. The login and signup actions are actually bound with mapActions, and the button click event is actually firing the one or the other, looking into the actual “loginSelected” value. So now, when a user does a signup, the actual application authentication store makes a call to the API and will create the new user. The same applies for the login.

In the User.vue view we can make the call to the addUser in the mounted hook of the component:

mounted() {
this.$store.dispatch("users/addUser", {username: this.userid});
}

when the component is open with a userid it will call the api for the details for this user and add it to the store.

In the SinglePost component, also, just update the delete button to be shown only for the posts of the actual user, it authenticated, like this:

<button        
v-if="loggedIn && currentUser.username === post.username"
class="delete-button"
@click.prevent="deletePost">

One last update, let’s change a little the application bar, so that when the logout button is clicked it automatically redirect the routing to the login page:

logoutButtonClicked() {
this.logout().then(() => {
this.$router.push({ name: "Login" });
});
}

Now the application start to take form, any user can register, add some posts, then delete his own posts, and see other users. Also, we have set up some conditions when the user can perform some actions, and whenever he is not authorized to do it (i.e. we decided that anyone can see the list of the posts, but only logged users can see the other users).

Note that actually a user is going to be logged out every 15 minutes, one nice way to fix this would be to renew the token in the client automatically and at regular time steps.

The next important step is to actually use a database instead of an array, server side, to store our data, and then our application would be almost fully working, but before we are going to save the authentication data in indexedDB in the browser of the user.

--

--

Ivano Dalmasso

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