Mastering Go Web Services - Sample Chapter
Mastering Go Web Services - Sample Chapter
ee
Sa
pl
Chapter 6, Accessing and Using Web Services in Go, explores ways to integrate
other web services for authentication and identity in a secure way.
Chapter 7, Working with Other Web Technologies, focuses on bringing in other
critical components of application architecture, such as frontend reverse proxy
servers and solutions, to keep session data in the memory or datastores for
quick access.
Chapter 8, Responsive Go for the Web, looks at expressing the values of our API
as a consumer might, but utilizing frontend, client-side libraries to parse and present
our responses.
Chapter 9, Deployment, introduces deployment strategies, including utilization of
processes to keep our server running, highly accessible, and interconnected with
associated services.
Chapter 10, Maximizing Performance, stresses upon various strategies for keeping our
API alive, responsive, and fast in production. We look at caching mechanisms that are
kept on disk as well as in memory, and explore ways in which we can distribute these
mechanisms across multiple machines or images.
Chapter 11, Security, focuses more on best practices to ensure that your application
and sensitive data are protected. We look at eliminating SQL injection and cross-site
scripting attacks.
[ 81 ]
In other words, in our early examples, calls to /api/users with OPTIONS should
return an indication that GET, POST, PUT, and DELETE are presently available options
at that REST resource request.
At present, there's no predefined format for what the body content should resemble
or contain although the specification indicates that this may be outlined in a future
release. This gives us some leeway in how we present available actions; in most such
cases we will want to be as robust and informative as possible.
The following code is a simple modification of our present API that includes some
basic information about the OPTIONS request that we outlined earlier. First, we'll add
the method-specific handler for the request in our exported Init() function of the
api.go file:
func Init() {
Routes = mux.NewRouter()
Routes.HandleFunc("/api/users", UserCreate).Methods("POST")
Routes.HandleFunc("/api/users", UsersRetrieve).Methods("GET")
Routes.HandleFunc("/api/users/{id:[0-9]+}",
UsersUpdate).Methods("PUT")
Routes.HandleFunc("/api/users", UsersInfo).Methods("OPTIONS")
}
[ 82 ]
Chapter 5
Calling this with cURL directly gives us what we're looking for. In the following
screenshot, you'll notice the Allow header right at the top of the response:
This alone would satisfy most generally accepted requirements for the OPTIONS verb
in the REST-based world, but remember that there is no format for the body and we
want to be as expressive as we can.
One way in which we can do this is by providing a documentation-specific package;
in this example, it is called specification. Keep in mind that this is wholly optional,
but it is a nice treat for any developers who happen to stumble across it. Let's take a
look at how we can set this up for self-documented APIs:
package specification
type MethodPOST struct {
POST EndPoint
}
type MethodGET struct {
GET EndPoint
}
type MethodPUT struct {
PUT EndPoint
}
type MethodOPTIONS struct {
OPTIONS EndPoint
}
type EndPoint struct {
Description string `json:"description"`
Parameters []Param `json:"parameters"`
}
type Param struct {
Name string "json:name"
ParameterDetails Detail `json:"details"`
}
type Detail struct {
Type string "json:type"
Description string `json:"description"`
Required bool "json:required"
[ 83 ]
You can then reference this directly in our api.go file. First, we'll create a generic
slice of interfaces that will encompass all the available methods:
type DocMethod interface {
}
Then, we can compile our various methods within our UsersInfo method:
func UsersInfo(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Allow","DELETE,GET,HEAD,OPTIONS,POST,PUT")
UserDocumentation := []DocMethod{}
UserDocumentation = append(UserDocumentation,
Documentation.UserPOST)
UserDocumentation = append(UserDocumentation,
Documentation.UserOPTIONS)
output := SetFormat(UserDocumentation)
fmt.Fprintln(w,string(output))
}
[ 84 ]
Chapter 5
The indentation system as a method of simulating code blocks will look familiar to
anyone with experience in Python.
There are a number of YAML implementations for Go.
The most noteworthy is go-yaml and this is available
at https://github.com/go-yaml/yaml.
TOML, or Tom's Obvious, Minimal Language, takes an approach that will look
very familiar to anyone who has worked with the .ini style config files.
[ 85 ]
And, when the structure is parsed against the text template, it will generate precisely
what we want as follows:{{.Name}}.
{{range $index, $value := Options}}
$index = $value
{{end}}
One big problem with this method is that you have no inherent system for
unmarshalling data. In other words, you can generate the data in this format,
but you can't unravel it back into Go structures the other way.
Another issue is that as the format increases in complexity, it becomes less reasonable
to use the limited control structures in the Go template library to fulfill all of the
intricacies and quirks of such a format.
[ 86 ]
Chapter 5
If you choose to roll your own format, you should avoid text templates and look
at the encoding package that allows you to both produce and consume structured
data formats.
We'll look at the encoding package closely in the following chapter.
Forcing HTTPS
At this point, our API is starting to enable clients and users to do some things,
namely create users, update their data, and include image data for these users.
We're beginning to dabble in things that we would not want to leave open in
a real-world environment.
The first security step we can look at is forcing HTTPS instead of HTTP on our API.
Go implements HTTPS via TLS rather than SSL since TLS is considered as a more
secure protocol from the server side. One of the driving factors was vulnerabilities in
SSL 3.0, particularly the Poodlebleed Bug that was exposed in 2014.
You can read more about Poodlebleed at https://poodlebleed.com/.
[ 87 ]
Let's look at how we can reroute any nonsecure request to its secure counterpoint in
the following code:
package main
import
(
"fmt"
"net/http"
"log"
"sync"
)
const (
serverName = "localhost"
SSLport = ":443"
HTTPport = ":8080"
SSLprotocol = "https://"
HTTPprotocol = "http://"
)
func secureRequest(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w,"You have arrived at port 443, but you are not
yet secure.")
}
This is our (temporarily) correct endpoint. It's not yet TSL (or SSL), so we're not
actually listening for HTTPS connections, hence the message.
func redirectNonSecure(w http.ResponseWriter, r *http.Request) {
log.Println("Non-secure request initiated, redirecting.")
redirectURL := SSLprotocol + serverName + r.RequestURI
http.Redirect(w, r, redirectURL, http.StatusOK)
}
This is our redirection handler. You'll probably take note with the http.StatusOK
status codeobviously we'd want to send a 301 Moved Permanently error (or an http.
StatusMovedPermanently constant). However, if you're testing this, there's a chance
that your browser will cache the status and automatically attempt to redirect you.
func main() {
wg := sync.WaitGroup{}
log.Println("Starting redirection server, try to access @
http:")
wg.Add(1)
[ 88 ]
Chapter 5
go func() {
http.ListenAndServe(HTTPport,
http.HandlerFunc(redirectNonSecure))
wg.Done()
}()
wg.Add(1)
go func() {
http.ListenAndServe(SSLport,http.
HandlerFunc(secureRequest))
wg.Done()
}()
wg.Wait()
}
So, why have we wrapped these methods in anonymous goroutines? Well, take them
out and you'll see that because the ListenAndServe function is blocking, we'll never
run the two simultaneously by simply calling the following statements:
http.ListenAndServe(HTTPport,http.HandlerFunc(redirectNonSecure))
http.ListenAndServe(SSLport,http.HandlerFunc(secureRequest))
Of course, you have options in this regard. You could simply set the first as a
goroutine and this would allow the program to move on to the second server.
This method provides some more granular control for demonstration purposes.
You can then take those files and move them wherever you want, ideally in the
directory where our API is running.
[ 89 ]
For the sake of being more explicit, let's also modify our secureRequest
handler slightly:
fmt.Fprintln(w,"You have arrived at port 443, and now you are
marginally more secure.")
If we run this now and go to our browser, we'll hopefully see a warning, assuming
that our browser would keep us safe:
Assuming we trust ourselves, which is not always advisable, click through and we'll
see our message from the secure handler:
[ 90 ]
Chapter 5
[ 91 ]
With this in place, let's take a look at our password package that will contain the salt
and hash generation functions:
package password
import
(
"encoding/base64"
"math/rand"
"crypto/sha256"
"time"
)
const randomLength = 16
func GenerateSalt(length int) string {
var salt []byte
var asciiPad int64
if length == 0 {
length = randomLength
}
asciiPad = 32
for i:= 0; i < length; i++ {
salt = append(salt, byte(rand.Int63n(94) + asciiPad) )
}
return string(salt)
}
[ 92 ]
Chapter 5
Here, we generate a hash based on a password and a salt. This is useful not
just for the creation of a password but also for validating it. The following
ReturnPassword() function primarily operates as a wrapper for other functions,
allowing you to create a password and return its hashed value:
func ReturnPassword(password string) (string, string) {
rand.Seed(time.Now().UTC().UnixNano())
salt := GenerateSalt(0)
hash := GenerateHash(salt,password)
return salt, hash
}
On our client side, you may recall that we sent all of our data via AJAX in jQuery.
We had a single method on a single Bootstrap tab that allowed us to create users.
First, let's remind ourselves of the tab setup.
And now, the userCreate() function, wherein we've added a few things.
First, there's a password field that allows us to send that password along when
we create a user. We may have been less comfortable about doing this before
without a secure connection:
function userCreate() {
action = "https://localhost/api/users";
postData = {};
postData.email = $('#createEmail').val();
postData.user = $('#createUsername').val();
postData.first = $('#createFirst').val();
postData.last= $('#createLast').val();
postData.password = $('#createPassword').val();
[ 93 ]
Next, we can modify our .ajax response to react to different HTTP status codes.
Remember that we are already setting up a conflict if a username or an e-mail ID
already exists. So, let's handle this as well:
var formData = new FormData($('form')[0]);
$.ajax({
url: action, //Server script to process data
dataType: 'json',
type: 'POST',
statusCode: {
409: function() {
$('#api-messages').html('Email address or nickname
already exists!');
$('#api-messages').removeClass
('alert-success').addClass('alert-warning');
$('#api-messages').show();
},
200: function() {
$('#api-messages').html('User created successfully!');
$('#api-messages').removeClass
('alert-warning').addClass('alert-success');
$('#api-messages').show();
}
},
Now, if we get a response of 200, we know our API-side has created the user.
If we get 409, we report to the user that the e-mail address or username is taken
in the alert area.
Examining OAuth in Go
As we briefly touched on in Chapter 4, Designing APIs in Go, OAuth is one of the more
common ways of allowing an application to interact with a third-party app using
another application's user authentication.
It's extraordinarily popular in social media services; Facebook, Twitter, and GitHub
all use OAuth 2.0 to allow applications to interface with their APIs on behalf of users.
[ 94 ]
Chapter 5
It's noteworthy here because while there are many API calls that we are comfortable
leaving unrestricted, primarily the GET requests, there are others that are specific to
users, and we need to make sure that our users authorize these requests.
Let's quickly review the methods that we can implement to enable something akin to
OAuth with our server:
Endpoint
/api/oauth/authorize
/api/oauth/token
/api/oauth/revoke
Given that we have a small, largely demonstration-based service, our risk in keeping
access tokens active for a long time is minimal. Long-lived access tokens obviously
open up more opportunities for unwanted access by keeping the said access open to
clients, who may not be observing the best security protocols.
In normal conditions, we'd want to set an expiry on a token, which we can do pretty
simply by using a memcache system or a keystore with expiration times.
This allows values to die naturally, without having to explicitly destroy them.
The first thing we'll need to do is add a table for client credentials, namely
consumer_key and consumer_token:
CREATE TABLE `api_credentials` (
`user_id` INT(10) UNSIGNED NOT NULL,
`consumer_key` VARCHAR(128) NOT NULL,
`consumer_secret` VARCHAR(128) NOT NULL,
`callback_url` VARCHAR(256) NOT NULL
CONSTRAINT `FK__users` FOREIGN KEY (`user_id`) REFERENCES
`users` (`user_id`) ON UPDATE NO ACTION ON DELETE NO ACTION
)
We'll check the details against our newly created database to verify credentials, and
if they are correct, we'll return an access token.
An access token can be of any format; given our low security restrictions for a
demonstration, we'll return an MD5 hash of a randomly generated string. In the real
world, this probably wouldn't be sufficient even for a short-lived token, but it will
serve its purpose here.
[ 95 ]
If you feed this key and secret value into the previously created table and
associate it with an existing user, you'll have an active API client. Note
that this may generate invalid URL characters, so we'll restrict our access
to the /oauth/token endpoint to POST.
Our pseudo OAuth mechanism will go into its own package, and it will strictly
generate tokens that we'll keep in a slice of tokens within our API package.
Within our core API package, we'll add two new functions to validate credentials
and the pseudoauth package:
import(
Pseudoauth "github.com/nkozyra/gowebservice/pseudoauth"
)
The functions that we'll add are CheckCredentials() and CheckToken(). The first
will accept a key, a nonce, a timestamp, and an encryption method, which we'll then
hash along with the consumer_secret value to see that the signature matches. In
essence, all of these request parameters are combined with the mutually known but
unbroadcasted secret to create a signature that is hashed in a mutually known way.
If those signatures correspond, the application can issue either a request token or
an access token (the latter is often issued in exchange for a request token and we'll
discuss more on this shortly).
In our case, we'll accept a consumer_key value, a nonce, a timestamp, and a
signature and for the time being assume that HMAC-SHA1 is being used as the
signature method. SHA1 is losing some favor do to the increased feasibility of
collisions, but for the purpose of a development application, it will do and can be
simply replaced later on. Go also provides SHA224, SHA256, SHA384, and SHA512
out of the box.
The purpose of the nonce and timestamp is exclusively added security. The nonce
works almost assuredly as a unique identifying hash for the request, and the
timestamp allows us to expire data periodically to preserve memory and/or storage.
We're not going to do this here, although we will check to make sure that a nonce has
not been used previously.
[ 96 ]
Chapter 5
To begin authenticating the client, we look up the shared secret in our database:
func CheckCredentials(w http.ResponseWriter, r *http.Request)
var Credentials string
Response := CreateResponse{}
consumerKey := r.FormValue("consumer_key")
fmt.Println(consumerKey)
timestamp := r.FormValue("timestamp")
signature := r.FormValue("signature")
nonce := r.FormValue("nonce")
err := Database.QueryRow("SELECT consumer_secret from
api_credentials where consumer_key=?",
consumerKey).Scan(&Credentials)
if err != nil {
error, httpCode, msg := ErrorMessages(404)
log.Println(error)
log.Println(w, msg, httpCode)
Response.Error = msg
Response.ErrorCode = httpCode
http.Error(w, msg, httpCode)
return
Here, we're taking the consumer_key value and looking up our shared consumer_
secret token, which we'll pass along to our ValidateSignature function as follows:
token,err := Pseudoauth.ValidateSignature
(consumerKey,Credentials,timestamp,nonce,signature,0)
if err != nil {
error, httpCode, msg := ErrorMessages(401)
log.Println(error)
log.Println(w, msg, httpCode)
Response.Error = msg
Response.ErrorCode = httpCode
http.Error(w, msg, httpCode)
return
}
Otherwise, we'll return the access code in a JSON body response. Here's the code for
the pseudoauth package itself:
package pseudoauth
import
(
"crypto/hmac"
"crypto/sha1"
"errors"
"fmt"
"math/rand"
"strings"
"time"
)
Nothing too surprising here! We'll need some crypto packages and math/rand to
allow us to seed:
type Token struct {
Valid bool
Created int64
Expires int64
ForUser int
AccessToken string
}
There's a bit more here than what we'll use at the moment, but you can see that we
can create tokens with specific access rights:
var nonces map[string] Token
func init() {
nonces = make(map[string] Token)
}
Chapter 5
fullyQualified := strings.Join(qualifiedMessage," ")
fmt.Println(fullyQualified)
mac := hmac.New(sha1.New, hashKey)
mac.Write([]byte(fullyQualified))
generatedSignature := mac.Sum(nil)
//nonceExists := nonces[nonce]
if hmac.Equal([]byte(signature),generatedSignature) == true {
t.Valid = true
t.AccessToken = GenerateToken()
nonces[nonce] = t
return t, nil
} else {
err := errors.New("Unauthorized")
t.Valid = false
t.AccessToken = ""
nonces[nonce] = t
return t, err
}
[ 99 ]
We'll need a few pieces to make this work, first, a login form for users who are
not presently logged in, by relying on a sessions table. Let's create a very simple
implementation in MySQL now:
CREATE TABLE `sessions` (
`session_id` VARCHAR(128) NOT NULL,
`user_id` INT(10) NOT NULL,
UNIQUE INDEX `session_id` (`session_id`)
)
Next, we'll need an authorization form for users who are logged in that allows us
to create a valid API access token for the user and service and redirects the user to
the callback.
The template can be a very simple HTML template that can be placed at /authorize.
So, we need to add that route to api.go:
Routes.HandleFunc("/authorize",
ApplicationAuthorize).Methods("POST")
Routes.HandleFunc("/authorize",
ApplicationAuthenticate).Methods("GET")
[ 100 ]
Chapter 5
Requests to POST will check confirmation and if all is well, pass this:
<!DOCTYPE html>
<html>
<head>
<title>{{.Title}}</title>
</head>
<body>
{{if .Authenticate}}
<h1>{{.Title}}</h1>
<form action="{{.Action}}" method="POST">
<input type="hidden" name="consumer_key"
value="{.ConsumerKey}" />
Log in here
<div><input name="username" type="text" /></div>
<div><input name="password" type="password" /></div>
Allow {{.Application}} to access your data?
<div><input name="authorize" value="1" type="radio">
Yes</div>
<div><input name="authorize" value="0" type="radio">
No</div>
<input type="submit" value="Login" />
{{end}}
</form>
</body>
</html>
Go's templating language is largely, but not completely, without logic. We can use
an if control structure to keep both pages' HTML code in a single template.
For brevity, we'll also create a very simple Page struct that allows us to construct
very basic response pages:
type Page struct {
Title string
Authorize bool
Authenticate bool
Application string
Action string
ConsumerKey string
}
We're not going to maintain login state for now, which means each user will need to
log in anytime they wish to give a third party access to make API requests on their
behalf. We'll fine-tune this as we go along, particularly in using secure session data
and cookies that are available in the Gorilla toolkit.
[ 101 ]
So, the first request will include a login attempt with a consumer_key value to
identify the application. You can also include the full credentials (nonce, and so
on) here, but since this will only allow your application access to a single user, it's
probably not necessary.
func ApplicationAuthenticate(w http.ResponseWriter, r
*http.Request) {
Authorize := Page{}
Authorize.Authenticate = true
Authorize.Title = "Login"
Authorize.Application = ""
Authorize.Action = "/authorize"
tpl := template.Must(template.New("main")
.ParseFiles("authorize.html"))
tpl.ExecuteTemplate(w, "authorize.html", Authorize)
}
All requests will be posted to the same address, which will then allow us to validate
the login credentials (remember GenerateHash() from our password package), and
if they are valid, we will create the connection in api_connections and then return
the user to the callback URL associated with the API credentials.
Here is the function that determines whether the login credentials are correct and if
so, redirects to the callback URL with the request_token value that we created:
func ApplicationAuthorize(w http.ResponseWriter, r *http.Request) {
username := r.FormValue("username")
password := r.FormValue("password")
allow := r.FormValue("authorize")
var dbPassword string
var dbSalt string
var dbUID string
uerr := Database.QueryRow("SELECT user_password, user_salt,
user_id from users where user_nickname=?",
username).Scan(&dbPassword, &dbSalt, &dbUID)
if uerr != nil {
}
[ 102 ]
Chapter 5
With the user_password value, the user_salt value, and a submitted password
value, we can verify the validity of the password by using our GenerateHash()
function and doing a direct comparison, as they are Base64 encoded.
consumerKey := r.FormValue("consumer_key")
fmt.Println(consumerKey)
var CallbackURL string
var appUID string
err := Database.QueryRow("SELECT user_id,callback_url from
api_credentials where consumer_key=?",
consumerKey).Scan(&appUID, &CallbackURL)
if err != nil {
fmt.Println(err.Error())
return
}
expectedPassword := Password.GenerateHash(dbSalt, password)
if dbPassword == expectedPassword && allow == "1" {
requestToken := Pseudoauth.GenerateToken()
authorizeSQL := "INSERT INTO api_tokens set
application_user_id=" + appUID + ", user_id=" + dbUID + ",
api_token_key='" + requestToken + "' ON DUPLICATE KEY UPDATE
user_id=user_id"
q, connectErr := Database.Exec(authorizeSQL)
if connectErr != nil {
} else {
fmt.Println(q)
}
redirectURL := CallbackURL + "?request_token=" + requestToken
fmt.Println(redirectURL)
http.Redirect(w, r, redirectURL, http.StatusAccepted)
[ 103 ]
After checking expectedPassword against the password in the database, we can tell
whether the user authenticated correctly. If they did, we create the token and redirect
the user back to the callback URL. It is then the responsibility of the other application
to store the token for future use.
} else {
fmt.Println(dbPassword, expectedPassword)
http.Redirect(w, r, "/authorize", http.StatusUnauthorized)
}
}
Now that we have the token on the third-party side, we can make API requests with
that token and our client_token value to make requests on behalf of individual
users, such as creating connections (friends and followers), sending automated
messages, or setting status updates.
Summary
We began this chapter by looking at ways to bring in more REST-style options and
features, better security, and template-based presentation. Towards this goal, we
examined a basic abstraction of the OAuth security model that allows us to enable
external clients to work within a user's domain.
With our application now accessible via OAuth-style authentication and secured
by HTTPS, we can now expand the third-party integration of our social networking
application, allowing other developers to utilize and augment our service.
In the next chapter, we'll look more at the client-side and consumer-side of our
application, expanding our OAuth options and empowering more actions via the
API that will include creating and deleting connections between users as well as
creating status updates.
[ 104 ]
www.PacktPub.com
Stay Connected: