Skip to content

Commit

Permalink
Add support for broker authentication (#303)
Browse files Browse the repository at this point in the history
  • Loading branch information
gnvk authored Sep 20, 2024
1 parent 48cd6c3 commit e486d60
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 17 deletions.
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# alpaca-trade-api-go

[![GitHub Status](https://github.com/alpacahq/alpaca-trade-api-go/actions/workflows/go.yml/badge.svg)](https://github.com/alpacahq/alpaca-trade-api-go/actions/workflows/go.yml)
Expand Down Expand Up @@ -105,6 +104,20 @@ export APCA_API_KEY_ID=xxxxx
export APCA_API_SECRET_KEY=yyyyy
```

### Broker auth

You use your Broker API key and secret for authentication.
However, for this to work make sure you're using the appropriate base URL
(for more details check the next section)!

```go
client := marketdata.NewClient(marketdata.ClientOpts{
BrokerKey: "CK...", // Sandbox broker key
BrokerSecret: "<your secret>", // Sandbox broker secret
BaseURL: "https://data.sandbox.alpaca.markets", // Sandbox url
})
```

## Endpoint

For paper trading, set the environment variable `APCA_API_BASE_URL` or set the
Expand All @@ -114,6 +127,15 @@ For paper trading, set the environment variable `APCA_API_BASE_URL` or set the
export APCA_API_BASE_URL=https://paper-api.alpaca.markets
```

### Broker API

For broker partners, set the base URL to

- `broker-api.alpaca.markets` for production
- `broker-api.sandbox.alpaca.markets` for sandbox
- `data.alpaca.markets` for production marketdata
- `data.sandbox.alpaca.markets` for sandbox marketdata

## Documentation

For a more in-depth look at the SDK, see the [package documentation](https://pkg.go.dev/github.com/alpacahq/alpaca-trade-api-go/v3).
21 changes: 13 additions & 8 deletions alpaca/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ import (

// ClientOpts contains options for the alpaca client
type ClientOpts struct {
APIKey string
APISecret string
OAuth string
BaseURL string
RetryLimit int
RetryDelay time.Duration
APIKey string
APISecret string
BrokerKey string
BrokerSecret string
OAuth string
BaseURL string
RetryLimit int
RetryDelay time.Duration
// HTTPClient to be used for each http request.
HTTPClient *http.Client
}
Expand Down Expand Up @@ -85,9 +87,12 @@ const (
func defaultDo(c *Client, req *http.Request) (*http.Response, error) {
req.Header.Set("User-Agent", Version())

if c.opts.OAuth != "" {
switch {
case c.opts.OAuth != "":
req.Header.Set("Authorization", "Bearer "+c.opts.OAuth)
} else {
case c.opts.BrokerKey != "":
req.SetBasicAuth(c.opts.BrokerKey, c.opts.BrokerSecret)
default:
req.Header.Set("APCA-API-KEY-ID", c.opts.APIKey)
req.Header.Set("APCA-API-SECRET-KEY", c.opts.APISecret)
}
Expand Down
29 changes: 29 additions & 0 deletions alpaca/rest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package alpaca

import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -41,6 +42,34 @@ func TestDefaultDo(t *testing.T) {
assert.Equal(t, "test body", string(b))
}

func TestDefaultDo_BrokerAuth(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if assert.NotEmpty(t, authHeader) && assert.True(t, strings.HasPrefix(authHeader, "Basic "), "%s is not basic auth", authHeader) {
key := authHeader[len("Basic "):]
b, err := base64.URLEncoding.DecodeString(key)
if assert.NoError(t, err) {
parts := strings.Split(string(b), ":")
assert.Equal(t, "broker_key", parts[0])
assert.Equal(t, "broker_secret", parts[1])
}
}
fmt.Fprint(w, "test body")
}))
c := NewClient(ClientOpts{
BrokerKey: "broker_key",
BrokerSecret: "broker_secret",
BaseURL: ts.URL,
})
req, err := http.NewRequest("GET", ts.URL+"/custompath", nil)

Check failure on line 64 in alpaca/rest_test.go

View workflow job for this annotation

GitHub Actions / lint

"GET" can be replaced by http.MethodGet (usestdlibvars)
require.NoError(t, err)
resp, err := defaultDo(c, req)
require.NoError(t, err)
b, err := io.ReadAll(resp.Body)
require.NoError(t, err)
assert.Equal(t, "test body", string(b))
}

func TestDefaultDo_SuccessfulRetries(t *testing.T) {
i := 0
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
Expand Down
21 changes: 13 additions & 8 deletions marketdata/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ import (
// Currently it contains the exact same options as the trading alpaca client,
// but there is no guarantee that this will remain the case.
type ClientOpts struct {
APIKey string
APISecret string
OAuth string
BaseURL string
RetryLimit int
RetryDelay time.Duration
APIKey string
APISecret string
BrokerKey string
BrokerSecret string
OAuth string
BaseURL string
RetryLimit int
RetryDelay time.Duration
// Feed is the default feed to be used by all requests. Can be overridden per request.
Feed Feed
// CryptoFeed is the default crypto feed to be used by all requests. Can be overridden per request.
Expand Down Expand Up @@ -97,9 +99,12 @@ func defaultDo(c *Client, req *http.Request) (*http.Response, error) {
req.Host = c.opts.RequestHost
}

if c.opts.OAuth != "" {
switch {
case c.opts.OAuth != "":
req.Header.Set("Authorization", "Bearer "+c.opts.OAuth)
} else {
case c.opts.BrokerKey != "":
req.SetBasicAuth(c.opts.BrokerKey, c.opts.BrokerSecret)
default:
req.Header.Set("APCA-API-KEY-ID", c.opts.APIKey)
req.Header.Set("APCA-API-SECRET-KEY", c.opts.APISecret)
}
Expand Down

0 comments on commit e486d60

Please sign in to comment.