SecureAuth API: Creating the Go SDK

Author: 
Jeff Hickman

 

Background

At SecureAuth, we are committed to providing world class strong authentication, with adaptive capabilities, in as many diverse integrations as possible. SecureAuth is dedicated to providing modern, well vetted, and easy to implement Software Development Kits (SDKs) for some of the largest programming languages in wide use at all organizations. With this mission in mind, I was offered the opportunity to help build an SDK for a modern language that we have fallen in love with at SecureAuth: Go (AKA: golang).

Go: A Little History

Go is a programing language developed at Google in 2007 by Robert Griesermer, Rob Pike, and Ken Thompson. It was created to address the large scale, concurrent services that Google was struggling to control with other well-known programming languages. Google quickly realized that Go had a much wider application outside of their own use, and announced in 2009 that Go would be free and open source for wider use. This allows any organization, enterprise, or hobbyist to build services, programs, web applications in Go by simply downloading and using the provided tools. In fact, some of the most disruptive technology to come out in recent years, utilize Go in their application set, such as Docker, CloudFlare, DropBox, Netflix, among others.

At its root, Go is a C derived language that compiles down to machine code, which makes it fast to compile and easy to use cross-platform. Go is a fast, statically typed, opinionated language, with the goal of making code that is easy to read, fast to develop, and require minimal cooperation between developers to make complete programs. With these features in mind, SecureAuth realized the immediate benefit and knew that our customers would as well.

SDK Design
Client

Building a SDK in a language such as Go is a bit of a challenge as the language is still very new (in relative terms) and some documentation can be hard to track down to provide specific functionality. This is also the beauty of the language, there is flexibility in how you implement features and choose to structure your code. The immediate goal with the Go SDK was to provide the tools needed to build the authentication headers that the SecureAuth API set uses to authenticate a program to use the APIs. The Go SDK for SecureAuth APIs offers a client package that provides the function to sign, build, and “Do” RESTful API requests. Provided in the client package is a “helper” function that makes it easy to instantiate a new Client struct, NewClient(). Here is a look at that function:

func NewClient(appId string, appKey string, host string, port int, realm string, ssl bool, bypassCert bool) (*Client, error) {

       params := []string{appId,appKey,host,realm}

       for _, v := range params {

              if isNil(v) {

                     return nil, errors.New(fmt.Sprintf("%v is required to create a new client.", v))

              }

       }

       c := new(Client)

       c.AppId = appId

       c.AppKey = appKey

       c.Host = host

       if port == 0 {

              c.Port = 443

       } else {

              c.Port = port

       }

       c.Realm = realm

       c.SSL = ssl

       c.BypassCertValidation = bypassCert

       return c, nil

}
 

As you can see, this function takes the needed information to build a request to the SecureAuth APIs and gives you a data structure that allows you to re-use the information to complete various extension functions provided, such as sign requests and build Get-Post-Put requests.

Modularity

The next design goal in mind was to make the SDK modular. This was easy to do with Go; We simply made each endpoint or functionality of the SecureAuth API a package in a service directory. This means that, using the source of the SDK, you can choose to include only the features you need. This allows a developer to keep the size and dependency footprint of a project small and manageable. Each of the service packages do require the Client data struct to execute requests, but the inverse is not true. The Client package can be the only package a developer uses and they can choose to implement their own functions, packages, or features to use SecureAuth APIs.

Helpers

Once the modularity of the design was completed, We were presented with a unique choice in how far we took the SDK to make things easier for a developer. Traditionally, Go encourages developers to implement their own code to handle the problem they are solving. We could have left each of the packages with just the required data structures, yet in our opinion any good SDK provides end to end functionality. With that self-imposed requirement, We added functions to each package to abstract away the need for any developer implementing the SDK to build their own logic for handling each API endpoint. For example:

func (r *Request) SendCallOtp(c *sa.Client, userId string, factorId string)(*Response, error){

       r.UserId = userId

       r.ReqType = "call"

       r.FactorId = factorId

       callResponse, err := r.Post(c)

       if err != nil {

              return nil, err

       }

       return callResponse, nil

}

In the example above, We created an extension method that allows the easy sending of a Voice Call to a user’s telephone number. A developer needs to provide the function parameters to complete the API call. However, We believe in choice and the cornerstone of any good SDK allows many ways for a developer to implement the framework. To this end, developers could choose to build a Request data struct, provide the needed data for the request type they want, and call the Get, Post, or Put function in the package to complete the API request. For example:

func (r *Request) Get(c *sa.Client, refId string)(*Response, error) {

       endpoint := buildEndpointPath(refId)

       httpRequest, err := c.BuildGetRequest(endpoint)

       if err != nil {

              return nil, err

       }

       httpResponse, err := c.Do(httpRequest)

       if err != nil {

              return nil, err

       }

       authResponse := new(Response)

       if err := json.NewDecoder(httpResponse.Body).Decode(authResponse); err != nil{

              return nil, err

       }

       authResponse.HttpResponse = httpResponse

       httpResponse.Body.Close()

       return authResponse, nil

}
Concurrency

One feature that We would like to highlight in Go is the built in support for concurrency. Concurrency allows developers to build applications that can run functions or complete packages in something called a goroutine. This allows multiple functions to be ran at the same time, without waiting for a result or response from the function. This is extremely useful in web servers that may be handling multiple requests at once and need to be responsive to each user and what they are doing in the application. We found a good spot to use this functionality in the SDK: Push to Accept reference checking. In the SecureAuth API when you send a Push to Accept two-factor method to a user’s device, a reference id is returned. This reference id is then used to make Get requests against an endpoint to poll for when a user has accepted, denied, or ignored the login request. Putting this reference check into a channel (or goroutine) lets the developer handle other logic while we wait for the user’s response. Here is that bit of code:

func (r *Request) CheckPushAcceptStatus(c *sa.Client, refId string, timeout int, interval int)(*Response, error) {

       tout := time.After(time.Duration(timeout) * time.Second)

       tick := time.Tick(time.Duration(interval) * time.Second)

       for {

              select {

              case <- tout:

                     return nil, errors.New("Request expired before response.")

              case <- tick:

                     checkResponse, err := r.Get(c, refId)

                     if err!= nil {

                            return nil, err

                     }

                     switch checkResponse.Message {

                     case "ACCEPTED", "DENIED", "FAILED", "EXPIRED":

                            return checkResponse, nil

                     case "PENDING":

                            continue

                     }

              }



       }

}

Now, this is a bit blocking from a code perspective, because we need the user response before we allow the authentication to proceed. However, placing this check into a channel allows the check to run in a much more responsive manner (overlaying requests before the previous one finished) and gives a better user experience (in our tests it was almost instant from when we pressed accept in our app) for accepting or denying Push to Accept.

Error Handling

The last code focused facet of this blog post revolves around a design feature of Go: errors and error handling. Traditionally, most programming languages provide a language syntax for handling errors, such as the try…catch pattern. With Go, errors are intended to be handled by the implementer and no such syntax is provided; We consider this a strength of the language. It gives the developers the ability to quickly prototype projects, build custom error logic, and due to the nature of multiple returns on functions, forces proper error handling. In the Go SDK for SecureAuth, each function has at least two returns, first is the expected struct or variable, then any error that was encountered. It is the developer’s job to properly handle these errors and build logic for what will happen in their application when an error is encountered.

Wrap Up

We hope that this SDK helps developers create innovative, modern, and secure applications with strong authentication! By the way, it is all open source! It can be found on GitHub at: https://github.com/SecureAuthCorp/saidp-sdk-go. If you have ideas for feature improvements, changes, enhancements, or bug fixes, feel free to fork, branch, and pull. Each change will be reviewed, tested and, if valid, added to the core SDK.

Resource Category: 
Topic: