Installation

Installing the Go Enforcer

The Go Enforcer is used as a middleware inside a Go web application.

1. Import the HUMAN Go Package.

GitHub Repository Access

If you do not have access the Go SDK GitHub Repository, please reach out to our Customer Support team.

To install the HUMAN Go Enforcer package, run the following command from within your Go project.

1go get github.com/perimeterx/perimeterx-go-sdk/perimeterx

2. Implement the Runtime interface.

The HUMAN Go Enforcer package requires you to implement a Runtime interface, which the Enforcer uses to perform HTTP requests, log messages, and obtain metadata about the server.

1type Runtime interface {
2 PerformSyncRequest(request *http.Request, timeout int) (*http.Response, error)
3 PerformAsyncRequest(request *http.Request, timeout int)
4 Log(message string)
5 GetOS() string
6 GetHostName() string
7}
  • PerformSyncRequest - Performs a synchronous HTTP request by sending the *http.Request argument and returning an *http.Response. If the provided timeout (in millseconds) is reached before the *http.Response can be returned, then an error should be returned instead.
  • PerformAsyncRequest - Performs an asynchronous request. The simplest way to implement this function is to call PerformSyncRequest from within a new goroutine.
  • Log - Logs the provided message argument.
  • GetOS - Returns the Operating System name as a string.
  • GetHostName - Returns the host name as a string.
Removed GetConfig() Function

In previous versions of the HUMAN Go Enforcer, the Runtime interface required the implementation of a GetConfig() function that returned a PxConfig struct. This function is no longer necessary and will not be used.

The ExampleRuntime struct below implements the perimeterx.Runtime interface using a single http.Client. However, you can implement this with a sync.Pool of http.Client structs as well.

1// initialize http client
2var client = &http.Client{
3 Transport: &http.Transport{
4 MaxIdleConnsPerHost: 10,
5 ResponseHeaderTimeout: 60 * time.Second,
6 }
7}
8
9// declare ExampleRuntime type
10type ExampleRuntime struct {}
11
12// implement PerformSyncRequest using http client
13func (runtime *ExampleRuntime) PerformSyncRequest(req *http.Request, timeout int) (*http.Response, error) {
14 client.Timeout = time.Duration(timeout) * time.Millisecond
15 return client.Do(req)
16}
17
18// implement PerformAsyncRequest by invoking PerformSyncRequest in new goroutine
19func (runtime *ExampleRuntime) PerformAsyncRequest(req *http.Request, timeout int) {
20 go runtime.PerformSyncRequest(req, timeout)
21}
22
23// implement GetOS using goruntime package
24func (runtime *ExampleRuntime) GetOS() string {
25 return goruntime.GOOS
26}
27
28// implement GetHostName using os package
29func (runtime *ExampleRuntime) GetHostName() string {
30 osName, _ := os.Hostname()
31 return osName
32}
33
34// implement Log using fmt package
35func (runtime *ExampleRuntime) Log(message string) {
36 fmt.Print(message)
37}

3. Build the HUMAN configuration.

In order to initialize the enforcer, you must first create a PxConfig struct instance. There are three ways of doing this.

All possible configuration key names and value types are listed in the Configuration section of the documentation.

Directly

The first way to do this is to use the BuildPXConfig() function. This function accepts a map[string]interface{} argument representing the configuration, and returns a pointer to a PxConfig struct (or an error if something went wrong).

1// build the configuration from map
2pxConfig, err := perimterx.BuildPXConfig(map[string]interface{}{
3 "px_app_id": "<APP_ID>",
4 "px_auth_token": "<AUTH_TOKEN>",
5 "px_cookie_secret": "<COOKIE_SECRET">,
6 // ...
7})

JSON File

You may store your configuration in a JSON file and load it into a PxConfig struct using the GetPxConfigFromJson() function. This function accepts a string representing the path to the JSON file, and returns a pointer to a PxConfig struct (or an error if something went wrong).

1// build the configuration from json
2pxConfig, err := perimeterx.GetPxConfigFromJson("/path/to/enforcer_config.json")

The minimum JSON file should look like this:

1// enforcer_config.json
2{
3 "px_app_id": "<APP_ID>",
4 "px_auth_token": "<AUTH_TOKEN>",
5 "px_cookie_secret": "<COOKIE_SECRET>",
6 // ...
7}

Builder

You may call NewPXConfigBuilder() to initialize a PXConfigBuilder struct, which has setters for all possible configuration values. Once all the desired configurations have been set, call the Build() function to receive a pointer to a PxConfig struct.

1// build the configuration with builder
2pxConfig := perimeterx.NewPXConfigBuilder(map[string]interface{}{
3 // must initialize with the required configurations only
4 "px_app_id": "<APP_ID>",
5 "px_auth_token": "<AUTH_TOKEN>",
6 "px_cookie_secret": "<COOKIE_SECRET">,
7 })
8 .SetModuleMode("active_blocking")
9 // ...
10 .Build()

4. Integrate the enforcer by adding a middleware.

Using the Default HUMAN Security Middleware

For an out-of-the-box middleware function, simply call the CreateDefaultHumanSecurityMiddleware function and pass in the PxConfig struct and Runtime implementation.

1func main() {
2 // build the configuration
3 pxConfig, err := perimterx.BuildPXConfig(map[string]interface{}{
4 "px_app_id": "<APP_ID>",
5 "px_auth_token": "<AUTH_TOKEN>",
6 "px_cookie_secret": "<COOKIE_SECRET">,
7 // ...
8 })
9 if err != nil {
10 fmt.Printf("failed to initialize enforcer configuration: %s", err)
11 return
12 }
13
14 // initialize the runtime
15 runtime := &ExampleRuntime{}
16
17 // create the HUMAN enforcement middleware
18 defaultMiddleware := perimeterx.CreateDefaultHumanSecurityMiddleware(pxConfig, runtime)
19
20 // use the HUMAN enforcement middleware to wrap HTTP handler functions
21 http.Handle("/", defaultMiddleware(http.FileServer(http.Dir("./static"))))
22 // ...
23
24 // serve
25 http.ListenAndServe(":3000", nil)
26}

Creating a Customized Middleware

For a more customized solution, initialize a HumanSecurityEnforcer struct and use it in your custom middleware function.

The recommended usage is to:

  • create the HumanSecurityEnforcer struct once (not on every middleware invocation) by calling NewHumanSecurityEnforcer()
  • Call the Enforce() function as soon as possible in the request handler and interpret the returned values
  • Set the PXHD cookie on the response if necessary by calling perimeterx.ShouldSetPxhd() and perimeterx.GetPxhdCookie()
  • If Enforce() returned a non-nil response, transfer it to the http.ResponseWriter and exit without invoking the provided handler
  • If Enforce() returned a nil response, invoke the provided handler
1// define global variables
2var enforcer *perimeterx.HumanSecurityEnforcer
3var runtime perimeterx.Runtime
4
5func main() {
6 // build the configuration
7 pxConfig, err := perimterx.BuildPXConfig(map[string]interface{}{
8 "px_app_id": "<APP_ID>",
9 "px_auth_token": "<AUTH_TOKEN>",
10 "px_cookie_secret": "<COOKIE_SECRET">,
11 // ...
12 })
13 if err != nil {
14 fmt.Printf("failed to initialize enforcer configuration: %s", err)
15 return
16 }
17
18 // initialize the runtime
19 runtime = &ExampleRuntime{}
20
21 // initialize the enforcer
22 enforcer = NewHumanSecurityEnforcer(pxConfig, runtime)
23
24 // use the HUMAN enforcement middleware to wrap HTTP handler functions
25 http.Handle("/", customMiddleware(http.FileServer(http.Dir("./static"))))
26 // ...
27
28 // serve
29 http.ListenAndServe(":3000", nil)
30}
31
32// define custom middleware that accepts an HTTP handler function
33func customMiddleware(handler http.HandlerFunc) http.HandlerFunc {
34 return func(w http.ResponseWriter, r *http.Request) {
35 // call the global enforcer's enforcement function
36 response, context, err := enforcer.Enforce(r, runtime)
37
38 // set pxhd cookie if needed
39 if perimeterx.ShouldSetPxhd(context) {
40 pxhdCookie := perimeterx.GetPxhdCookie(context)
41 http.SetCookie(w, &pxhdCookie)
42 }
43
44 // if there was an error, log it
45 if err != nil {
46 runtime.Log(fmt.Sprintf("enforcer error: %s", err))
47 }
48
49 if response != nil {
50 // if the response exists...
51 // write the response status code, headers, and body with the http.ResponseWriter
52 for key, value := range response.Header {
53 w.Header().Set(key, value[0])
54 }
55 var body, _ = io.ReadAll(response.Body)
56 w.WriteHeader(response.StatusCode)
57 w.Write(body)
58 } else {
59 // if the response does not exist...
60 // invoke the provided handler
61 handler(w, r)
62 }
63 }
64}
Using Credential Intelligence?

The Credential Intelligence product also requires the sending of an additional_s2s activity, which indicates whether the login attempt was successful. To properly determine whether the login attempt was successful and send the additional_s2s activity, use the perimeterx.NewResponseWriterWrapper() and PostEnforce() functions. See code sample below.

1func customMiddleware(handler http.HandlerFunc) http.HandlerFunc {
2 return func(w http.ResponseWriter, r *http.Request) {
3
4 // enforcement, error handling, setting pxhd, etc...
5
6 if response != nil {
7 // ...
8 } else {
9 // if the response does not exist...
10 // create a new response writer wrapper to store the response body
11 responseWriterWrapper := perimeterx.NewResponseWriterWrapper(w)
12
13 // defer an invocation to send the additional_s2s activity
14 defer enforcer.PostEnforce(responseWriterWrapper, context, runtime)
15
16 // invoke the provided handler with the response writer wrapper
17 handler(responseWriterWrapper, r)
18 }
19 }