TodoList App – Part 3

Reading Time: 4 minutes

In the last article we added a nosql database and were able to register and login a registered user through the REST api. In this tutorial we’ll extend that functionality to use third party login , viz Google Sign-in.  This is because not m any users would want to fill up a form, plus this also alleviates the problem of verifying a user’s email since that would be taken care of by third-party Sign-in service.

Adding JWT to REST api

First we’ll add JWT to our REST api. The purpose here is to avoid a database query every time a resource needs to be accessed by an authorized user. You can learn more about JWT here. I’ll be using this JWT Go package for adding JWT capability to our REST api. These are the changes we’ll add in this article

  • Restrict API usage to correct request format. Viz, GET, POST, PUT etc.
  • Restrict API usage only to HTTPS scheme.
  • Add JWT token based authorisation once user has successfully logged in.
  • Introduce Google-Sign in for registration, login and subsequent authorization via JWT.

Restricting API Usage Based on HTTP Method

This is easy, simply decide what method we want to use on an endpoint and generate error if it’s not that method. The code below shows how this is done. You can use this tag to follow along with the code in this article.

func checkRequestMethod(w *http.ResponseWriter, r *http.Request, method string) bool {
	if r.Method != method {
		response := responses.Response{
			Status:  http.StatusBadRequest,
			Message: "HTTP Method Not Supported",
		}
		GenericWriteResponse(w, &response)
		return false
	}
	return true
}
 

I’ve added some helper function and a generic response type which is used to convey back the status and the message to the user. The method parameter should be one of the standard http.MethodXXX values rather than writing your own version.

Currently the API only supports the methods GET and POST and to make it easier to check for the headers needed in each request type I added a map variable which contains if a header is needed and if it is, what’s the value to look for. It looks like the following on this tag of code,

//Map the required headers based on whether they are mandatory or not.
//Most of the time a header would contain only a single value but we
//make it an array here to allow for multiple values if any.
type requestHeadersRequired struct {
	Name     string
	Values   []string
	Required bool
}

var headersRequired = map[string][]requestHeadersRequired{
	http.MethodGet: {
		{Name: "Content-Type", Values: []string{"application/json"}, Required: true},
	},
	http.MethodPost: {
		{Name: "Content-Type", Values: []string{"application/json"}, Required: true},
		{Name: "Authorization", Values: []string{"Bearer"}, Required: true},
		{Name: "X_Resource_Auth", Values: []string{""}, Required: false},
	},
}
 

Redirection to HTTPS scheme

I’m using Heroku to deploy my application. For Web Apps, Heroku forwards the traffic to the web application internally using the HTTP scheme. The public facing endpoint of the web application however has both HTTP and HTTPS versions. What we want is to make sure that web clients which use HTTP should be redirected to HTTPS scheme of the public facing application endpoint. This is done as follows,

//Heroku gives a header named X-Forwarded-Proto
//which contains the scheme the request originally
//landed on heroku server. NOTE that we don't run
//a HTTPS server, all requests come to us as plain
//HTTP request since it's forwarded internally by
//Heroku to us.
func redirectToHTTPS(w *http.ResponseWriter, r *http.Request) bool {
	scheme, ok := r.Header[utils.HerokuForwardedProto]
	//We're not running behind Heroku or a
	//Cloud based host that supports X-Forwarded-Proto.
	if !ok {
		return false
	}
	//The magic http code is 307 which causes http clients
	//to re-issue request with the correct http method.
	//Not using 307 causes some clients to change the original
	//http method to POST by default.
	if scheme[0] != "https" {
		httpsURL := fmt.Sprintf("https://%s%s", r.Host, r.URL.Path)
		if len(r.URL.RawQuery) > 0 {
			httpsURL = fmt.Sprintf("%s?%s", httpsURL, r.URL.RawQuery)
		}
		http.Redirect(*w, r, httpsURL, http.StatusTemporaryRedirect)
		return true
	}
	return false
} 

In the above code snippet, the redirection happens when we write 307, which is temporarily redirect instead of moved permanently (301). The reason being client like curl don’t use the original request method if they don’t see 307. Ofcourse we still would need to enable follow redirect (-L for curl) on the client for this to work automatically.

Adding JWT to REST API

The idea behind JWT is to minimize the payload and database accesses needed to authorise a user. Once a user has logged in we can use JWT to authorise access to resource , or REST api endpoints in our case.

I’m using this JWT Go package but feel free to use anything you like and modify the code accordingly. In order to create JWT tokens we need a way to identify users, thus I added this struct as shown below

//We'll store the userid currently.
//AppClaim is a wrapper over the standard
//jwt claim.
type AppClaim struct {
	Resource  string          `json:"res"`
	Id        string          `json:"id"`
	TokenType model.LoginType `json:"token_type"`
	jwt.StandardClaims
} 

The Id stores the user id so we can identify which user is going to be accessing the resource. If we’re able to successfully parse and verify the JWT token then we can be sure the user is who she/he claims to be. For this purpose we’ve to

  • Keep the token validity short. I’m using 15 minutes for this.
  • Use a secret key to generate token.

Obviously the secret key can’t be in the code, hence I’m again using Heroku’s environment variables to store that key. The function below uses that key

func getSigningKey() string {
	return base64.StdEncoding.EncodeToString([]byte(environment.GetAppTokenSecret()))
}

func GenerateTokenWithTimeout(user *model.User, timeout int64, tokenType model.LoginType) (string, error) {
	appClaim := AppClaim{
		Resource: "login",
		Id:       user.ID,
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: timeout,
			Issuer:    environment.GetAppName(),
			NotBefore: time.Now().Unix(),
		},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, appClaim)
	signedToken, err := token.SignedString([]byte(getSigningKey()))
	if err != nil {
		return "", err
	}
	log.Printf("Generated %s for user %s", signedToken, user.ID)
	return signedToken, nil
} 

NOTE

You must add your secret in your Heroku app for this to work properly. The default secret key would be an empty string if you don’t set one. To add 

heroku config:set -a <your_app>  APP_TOKEN_SECRET="<your_token_secret>" 

Try it out

You can use the following command to check how the redirects and JSON responses are coming back from the REST API, the test data is just a file containing the JSON format our REST API expects to see. You can use the register endpoint first to register your user and then use login endpoint as shown below. Make sure you’ve the -L option so that curl follows the redirect from server

Try adding or removing request headers and view the response from our REST api.

curl -d "$(cat test_data/user_login.data)" \
sheltered-badlands-62293.herokuapp.com/login \
-vvvv -L -H "Content-Type: application/json" -H "Authorization: Bearer" 

The test data for registration is shown as below,

{
    "id" : "<your_user_id>",
    "pass" : "<your_pass">,
    "meta" : {
        "anything": "here",
        "some": "more"
    }
} 

In the next article, I’ll start work on the Android app and integrate Google-Sign in with our REST API. Once we’ve our basic App deployed we should be able to make more changes both to the Android App and to the REST API.

Leave a Reply