A practical way to automate testing Oauth 2.0 Service

September 01, 2018 by RestBird

Nowadays Oauth 2.0 has become the most commonly used authentication framework for Restful API services. Among the different grant types, The Authorization Code Grant Type is probably the most common of the OAuth 2.0 grant types that you’ll encounter. It is used by both web apps and native apps to get an access token after a user authorizes an app.

[OAuth 2.0 Framework - RFC 6749] (https://tools.ietf.org/html/rfc6749)

[A complete guide to Oauth 2.0 grant] (https://alexbilbie.com/guide-to-oauth-2-grants/)

[What is the OAuth 2.0 Authorization Code Grant Type]
(https://developer.okta.com/blog/2018/04/10/oauth-authorization-code-grant-type)

How to automate testing this kind of service, especially for CI/CD?

Candidate solutions include, first you may think of Selenium, or other scripting way to stimulate user interactions with the web page and calling the automation scripts when they are needed. Selenium can sure solve some of the situation, however, the complexity of Selenium coding for various login pages, tricky way of solving different of katcha and the poor mentianessabiliy because of the UI tend to change often, all of these can become the real headache.

[Oauth Automation Example with SoapUI by stimulating user interaction] (https://support.smartbear.com/readyapi/docs/projects/requests/auth/types/oauth2/automate/sample.html)

Here we’re going to introduce how Restbird solve this particular case using its global environment and task.

Case Study: Box API Testing automation

Take Box API for example, Box uses the standard OAuth 2 three-legged authentication process, which proceeds as follows:

  • The application asks an authentication service to present a login request to a user.
  • After a successful login, the authentication service returns an authorization code to the application.
  • The application exchanges the authorization code for an access token.

[Box OAuth2 Authentication] (https://developer.box.com/reference)

Create new Rest API Case for Box

How to create test cases for restful API

Add Authorize API to get authorization code

  • Create rest API to get authorization code
Method GET
Url https://account.box.com/api/oauth2/authorize
Url parameter
  • response_type: code
  • client_id: {{client_id}}
  • redirect_uri: https://localhost
  • state: BOX

Here is how the request looks in Restbird

Box Get authorization code

  • Click “Run test”, and then Copy the Url into web browser

Run Box get authorization code API

  • Enter user credentiaL and click “Authorize”

Box User login

  • Click “Grant access to Box”

Box user grant access

  • The browser will then redirect to “redirect_ur” as the request specified, in this case, it’s https://localhost, with the authorization code as the “code” parameter

Box code url

Add the Get access token API to get access token

  • Create rest API to get access token
Method POST
Url https://api.box.com/oauth2/token
Req Body
(application/x-www-form-urlencoded)
  • grant_type: authorization_code
  • client_id: {{client_id}}
  • rclient_secret: {{client_secret}}
  • code: Copy Code from last API

Here is how the request looks in Restbird

Box Get access token

Here golang is used as smaple language, access taken and referesh token will be extracted from response json data and saved into global environment.

callapi is Restbird defined golang lib which has variaty of APIs for core scripting functionality.

package api
import "callapi"
import "net/http"
import "io/ioutil"
import	"encoding/json"
import "fmt"

type MyDATA struct {
	Access_token    string `json:"access_token,omitempty"`
	Refresh_token    string `json:"refresh_token,omitempty"`
}

func (c  CallBack) ResponseValidate(resp *http.Response, ctx *callapi.RestBirdCtx) bool {
    var body []byte
    var err error
    var data MyDATA = MyDATA{}
    
	if body, err = ioutil.ReadAll(resp.Body); err != nil {
	    fmt.Println("read body failed.")
		return true
	} 
	if err = json.Unmarshal(body, &data); err != nil {
	    fmt.Println("conver body to json failed")
		return true
	}	
	
	callapi.SetGlobalString("box_access_token", data.Access_token)
	callapi.SetGlobalString("box_refresh_token", data.Refresh_token)
	
	_, box_access_token := callapi.GetGlobalString("box_access_token")
	_, box_refresh_token := callapi.GetGlobalString("box_refresh_token")
	
	fmt.Println("box_access_token: " + box_access_token)
	fmt.Println("box_refresh_token: " + box_refresh_token)
	
	return true
}
  • After clicking “run test”, you can see refresh_token in response body.

Box response with token

click ”Globals” to verify that the variables have been saved into global enviroment.

view global env

Add the Refresh access token API to refresh access token

  • Create rest API to refresh access token
Method POST
Url https://api.box.com/oauth2/token
Req Body
(application/x-www-form-urlencoded)
  • grant_type: refresh_token
  • client_id: {{client_id}}
  • rclient_secret: {{client_secret}}
  • code: {{box_refresh_token}}

{{}} is the syntax for using both local and global environment variables.

Here is how the request looks in Restbird

Box req referesh token

Similar as the get access token API, after referesh token API been called, the two gloabl variables box_access_token and box_referesh_token need to be updated accordingly.

package api
import "callapi"
import "net/http"
import "io/ioutil"
import	"encoding/json"
import "fmt"

type MyDATA struct {
	Access_token    string `json:"access_token,omitempty"`
	Refresh_token    string `json:"refresh_token,omitempty"`
}

func (c  CallBack) ResponseValidate(resp *http.Response, ctx *callapi.RestBirdCtx) bool {
    var body []byte
    var err error
    var data MyDATA = MyDATA{}
    
	if body, err = ioutil.ReadAll(resp.Body); err != nil {
	    fmt.Println("read body failed.")
		return true
	} 
	if err = json.Unmarshal(body, &data); err != nil {
	    fmt.Println("conver body to json failed")
		return true
	}	
	
	callapi.SetGlobalString("box_access_token", data.Access_token)
	callapi.SetGlobalString("box_refresh_token", data.Refresh_token)
	
	_, box_access_token := callapi.GetGlobalString("box_access_token")
	_, box_refresh_token := callapi.GetGlobalString("box_refresh_token")
	
	fmt.Println("box_access_token: " + box_access_token)
	fmt.Println("box_refresh_token: " + box_refresh_token)
	
	return true
}

Create Task to automate token retrival

Up to now, we have already done creating all the pieces, now there need a glue to hold everything together. Here comes task to complete the job, task is pure script that can do everything. In this example, we use task to implement a timer, inside the timer function, we call the refresh token API to periodically update the two gloabl variables box_access_token and box_referesh_token, so that we can have valid token as long as the Restbird server is up.

package main
import "callapi"
import "fmt"
import "time"

import api2 "restbird/Box/Token/api2"

func main() {
		for {
			select {
				case <-time.Tick(time.Millisecond * 60000 * 60 ):
				mytime := time.Now()
				callapi.SetGlobalVars("starttime", mytime)
				callapi.DoHttpRequestWithEnv("Box/Token", "api2", api2.CallBack{},  "Box")
				
				callapi.GetGlobalVars("starttime", &mytime)
				fmt.Println("starttime: ", mytime, "current Time:", time.Now())
				
				_, box_access_token:= callapi.GetGlobalString("box_access_token")
				fmt.Println("++box_access_token: " + box_access_token, "\n")

				_, box_refresh_token:= callapi.GetGlobalString("box_refresh_token")
				fmt.Println("--box_refresh_token: " + box_refresh_token, "\n\n")
			}
		}
}
  • import api2 “restbird/Box/Token/api2” , the syntax of reference a Rest API is restbird/[path to rest case]/[rest case]/api[index], where index starts from 0.
  • callapi.DoHttpRequestWithEnv(“Box/Token”, “api2”, api2.CallBack{}, “Box”), if local environment has been used in the API, DoHttpRequestWithEnv need to be called with the name of the environment passed as the last argument.

In this article, we have introduced a way to automatically retrieve the access token for testing oauth 2.0 service, where user only need to login once to get the autorization code, after that, the Restbird test server will keep refereshing the token in given time interval to maintain a valid token.

Download the example project from github