Go GraphQL: Adding JWT Authentication to GraphQL API in Golang #6

In this post we will setup the JWT token authentication for our GraphQL API to authenticate the users



In this post, we are going to see how to protect particular GraphQL API using JSON web tokens that allow the users to authenticate with jwt tokens.
we will be;
  • We will add a mutation that allows obtaining the token with username/password
  • We will authenticate the users using the token in out resolvers

One of the most popular ways to enforce some kind of authorization in an application is through the use of JSON web tokens (JWT).

Users authenticate with a service and the service responds with a JWT to be used in every future request so that way the password is kept safe. The service can then validate the JWT to make sure it is correct and not expired.

So lets get started!
. . .

Generating and Validating JSON Web Tokens

Before we start adding code to the project, let’s create the project and download any project dependencies that will be used. With the project created, execute the following from the command line:
glide get github.com/dgrijalva/jwt-go

jwt-go package will allow us to sign and validate JSON web tokens.

Now that we have our project created and the appropriate dependencies in hand, let’s start adding some code. We’re going to start by adding our boilerplate code and the logic for creating and validating JSON web tokens.

Lets create the JWT token:

func CreateToken(username, password string) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"username": username,
"password": password,
})
tokenString, err := token.SignedString(jwtSecret)
if err != nil {
return "", gqlerrors.FormatError(err)
}

return tokenString, nil
}

What we’re doing is taking a username and password and creating a JWT token from it. Then we sign this token using our secret and return the signed token back as a response.

If you really wanted to be production ready, you would not include the password in the token and you’d probably add an expiration time that you check when you validate. We’re not going to worry about any of this for our example.

Validating the JWT tokens:

func ValidateToken(t string) (bool, error) {
if t == "" {
return false, gqlerrors.FormatError(errors.New("Authorization token must be present"))
}

token, _ := jwt.Parse(t, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("There was an error")
}
return jwtSecret, nil
})

if _, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
return true, nil
} else {
return false, gqlerrors.FormatError(errors.New("Invalid authorization token"))
}
}

Our ValidateToken function will validate the given token string. If no token is present, we’re going to respond with an error, otherwise, we’re going to parse the token using our secret.

If the token is valid we’re going to decode it and return the decoded value, otherwise, we’re going to return an error.

We will be extending out GraphQL schema, where users can obtain a token by providing the valid username and password:

Mutation := graphql.NewObject(graphql.ObjectConfig{
Name: "Mutation",
Fields: graphql.Fields{
#1
"createToken": &graphql.Field{
Type: graphql.String,
Description: "creates a new JWT token ",
#2
Args: graphql.FieldConfigArgument{
"username": &graphql.ArgumentConfig{
Description: "username",
Type: graphql.NewNonNull(graphql.String),
},
"password": &graphql.ArgumentConfig{
Description: "password",
Type: graphql.NewNonNull(graphql.String),
},
},
#3
Resolve: func(params graphql.ResolveParams) (interface{}, error) {
// marshall and cast the argument value
username, _ := params.Args["username"].(string)
password, _ := params.Args["password"].(string)
#4
token, err := auth.CreateToken(username, password)
if err != nil {
return nil, err
}
return token, nil
},
},
},
})

Let's walk through the comments to understand what's going on :
  1. We have added a createToken mutation that allows obtaining the token
  2. It accepts to arguments username & password
  3. Wrote the resolver function for the createToken
  4. CreeateToken function is called that returns the token with given username and password

So, let's create the token
mutation {
createToken(username: "mike", password: "so-secret")
}

It will return the following result
{
"data": {
"createToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXNzd29yZCI6InNvLXNlY3JldCIsInVzZXJuYW1lIjoibWlrZSJ9.SXr1CDpd93A3u9NpyxNbTZk5Ryk-e379_5jYu1J01V0"
}
}

Now we have successfully added the mutation that allows the users to create token with given username and password, which can be used for authentication purpose
. . .

Passing JWT token in HTTP Headers

Now we have the jwt token that can be used to authenticate the users. But how? How we are going pass this token to graphql resolvers to authenticate. We are going to make the following changes in our code
  • We need to make changes in our HTTP server to accept the token from the headers
  • Then pass these headers to the GraphQL go-graphql context
  • We can access the context in any resolvers and can easily validate the user

So lets edit our HTTP server code to accept the headers as follows

package main

import (
"context"
... more impoerts
)

func main() {
... more code
http.HandleFunc("/graphql", graphqlHandler)
... more code
}

#1
func graphqlHandler(w http.ResponseWriter, r *http.Request) {

switch r.Method {
case "POST":
body, err := ioutil.ReadAll(r.Body)
if err != nil {
panic(err)
}
#2
type GraphQLPostBody struct {
Query string `json:"query"`
Variables map[string]interface{} `json:"variables"`
OperationName string `json:"operationName"`
}
#3
var graphQLPostBody GraphQLPostBody
err = json.Unmarshal(body, &graphQLPostBody)
if err != nil {
panic(err)
}
#4
token := r.Header.Get("token")

#5
result := graphql.Do(graphql.Params{
Schema: schema.Schema,
RequestString: graphQLPostBody.Query,
VariableValues: graphQLPostBody.Variables,
OperationName: graphQLPostBody.OperationName,
Context: context.WithValue(context.Background(), "token", token),
})
json.NewEncoder(w).Encode(result)

default:
fmt.Fprintf(w, "Sorry, only POST method are supported.")
}
}

Let's walk through the comments to understand it better:
  1. Added a graphqlHandler for our /graphql, and only POST request is accepted at this endpoint.
  2. Created the appropiate struct to handle incoming POST request body
  3. http request is descerialized to struct
  4. token is extracted from the header with the key `token`
  5. token is passed into the context on graphql handler with query and params

Now token can be accessed into any resolvers as follows:

Resolve: func(p graphql.ResolveParams) (interface{}, error) {
token := p.Context.Value("token").(string)
}

Let's pass the token with the headers from the graphql playground

You can create the token and then set into the headers, which will sent to the graphql server with every request you fire!
. . .

Protecting GraphQL Query/Mutation with an Authorization token

We’ve got our JWT logic and we’ve got our Human resolver in place. Now we need to authenticate the incoming request to the GraphQL server.

We need to make the following changes in our Human resolver to authenticate the incoming request as follows

Query := graphql.NewObject(graphql.ObjectConfig{
Name: "Query",
Fields: graphql.Fields{
"human": &graphql.Field{
Type: types.HumanType,
Args: graphql.FieldConfigArgument{
... args
},
#1
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
#2
token := p.Context.Value("token").(string)
// validate token
#3
isValid, err := auth.ValidateToken()
if err != nil {
return nil, err
}
if !isValid {
return nil, gqlerrors.FormatError(errors.New("Invalid token"))
}

#4
char, err := resolvers.GetHuman(id)
if err != nil {
return nil, err
}

return char, nil
},
},
},
})
Let's walk through the comments to understand the code
  1. We have the resolver function that resolvers the human query
  2. Token can be accessed inside the context of resolver with `token` key
  3. Token is validated with the auth helper function, it will return an error if the token is invalid
  4. If the token is valid then GetHuman is called which returns the human data
Let's run the below query to see the authentication in action, but don't forget to pass the valid token into the header
{
human (id: "1001") {
id
name
appearsIn
homePlanet
}
}

and you will see the following output
{
"data": {
"human": {
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
],
"homePlanet": "Tatooine",
"id": "1001",
"name": "Darth Vader"
}
}
}


What happens if the token is invalid? let's see

If you pass the invalid authorization token into the headers, it will fail to authenticate and will give the authentication error!

So now we have successfully implemented the JWT authorization in our GraphQL API
. . .


Never miss a post from Gufran Mirza, when you sign up for Ednsquare.