sand

package module
v2.0.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 2, 2023 License: MIT Imports: 18 Imported by: 0

README

sand-go

Go library for service authentication via OAuth2

A client who wants to communicate with a service, it will request a token from the OAuth2 server and use this token to make an API call to the service.

When a service receives a request with an OAuth bearer token, it verifies the token with the OAuth2 server to see if the token is allowed to access this service. The service acts like an OAuth2 Resource Server that verifies the token.

Features

  • The authentication is performed using the "client credentials" grant type in OAuth2.
  • The tokens can be cached on both the client and the service sides. The cache store is configurable by providing an adapter to the cache interface.

Instruction

Warning: A cache must be used for the client or the service to cache tokens and verification results up to a certain time defined by the OAuth2 server.

A client that intends to communicate with a service can use sand.Client to request a token from an OAuth2 server. A client can be created via the NewClient function:

//ClientID: The client ID of the OAuth2 client credentials
//ClientSecret: The client secret of the OAuth2 client credentials
//TokenURL: The token endpoint of the OAuth2 server, e.g., "https://oauth.example.com/oauth2/token"
client := sand.NewClient("ClientID", "ClientSecret", "TokenURL")

//Below shows the optional fields (with their default values) that can be modified after a client is created
client.SSLMinVersion     = tls.VersionTLS12 // Minimum version of SSL supported
client.DefaultRetryCount = 2       // Default number of retries on request error
client.CacheRoot         = "sand"  // A string as the root namespace in the cache
client.Verbose           = true   // Verbose option can be used to enable/disable extra logs.

// The Request function has the retry mechanism to retry on 401 error.
client.Request("cache-key", []string{"scope1", "scope2"}, func(token string) (*http.Response, error) {
  // Make http request with "Bearer {token}" in the Authorization header
  // return the response and error
})

A service that receives a request with the OAuth2 bearer token can use sand.Service to authorize the token with the OAuth2 server. A service can be created via the NewService function:

//The first four arguments are the same as those of sand.NewClient
//Resource: The resource name that identifies this service and is registered with the OAuth2 server
//TokenVerifyURL: The URL of the token verification endpoint, e.g., "https://oauth.example.com/warden/token/allowed"
service := sand.NewService("ClientID", "ClientSecret", "TokenURL", "Resource", "TokenVerifyURL", []string{"Scopes"})

//Usage Example with Gin 1:
//In order for a service to verify the token with customized data rather than
//the defaults, define a VerificationOption and use the "VerifyRequest" function.
func(c *gin.Context) {
  numRetry := 2
  options := sand.VerificationOption{
    TargetScopes: []string{"target_scope1"},
    Resource: "a:b:c:resource",
    Action: "any",
    Context: map[string]interface{}{},
    NumRetry: &numRetry,
  }
  response, err := sandService.VerifyRequest(c.Request, options)
  if err != nil || response["allowed"] != true {
    c.JSON(sandService.ErrorCode(err), err)
  }
  ...
}

//Usage Example with Gin 2:
func(c *gin.Context) {
  response, err := sandService.CheckRequest(c.Request, []string{"scope1", "scope2"}, "action")
  if err != nil || response["allowed"] != true {
    c.JSON(sandService.ErrorCode(err), err)
  }
  ...
}
Client

sand.Client has the Request method which can perform retry when encountering 401 responses from the service. This should be the primary method to use for a client.

Both sand.Client and sand.Service have the Token function that gets an OAuth token from authentication service. If a cache store is available and the token is found in cache, it will return this token and not retrieving the token from the authentication service.

Service

sand.Service defines the VerifyRequest and CheckRequest functions for verifying an http.Request with the authentication service on whether the client token in the request is allowed to communicate with this service. A client's token and the verification result will also be cached if the cache is available.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (

	// This margin should be the max time it takes for service to verify a token.
	// The estimated time values:
	//   1. 2 second:
	//      Sand issued a token and respond the token back to the client
	//   2. 5 seconds:
	//      Client opens a connection and sends a request to the service
	//   3. 5 + 5 = 10 seconds:
	//      Service requests a service token from Sand. The default open_timeout (5 secs) plus timeout (5 secs)
	//   4. 5 + 5 = 10 seconds:
	//      Service makes request to the Sand verification endpoint. The default open_timeout (5 secs) plus timeout (5 secs)
	RequireMinimumValidityTimeOnToken = 27.0
	// Margin in seconds to compensate the possible delay on the expires_in seconds
	// from Sand server.
	TokenCompensateMargin = 3 * time.Second

	CacheMaxJitter = 15.0
)

Functions

func ExponentialRetryDuration

func ExponentialRetryDuration(retry int) time.Duration

func ExtractToken

func ExtractToken(authHeader string) string

ExtractToken extracts a bearer token from the Authorization header. The "bearer" keyword is case-insensitive

func NewTokenOption

func NewTokenOption() *tokenOption

func SetSecondaryCache

func SetSecondaryCache(secondaryCache cache.Cache)

SetSecondaryCache sets or replaces the secondary cache used by Sand clients and services.

Types

type Client

type Client struct {
	//The client ID of the OAuth2 client credentials
	ClientID string
	//The client secret of the OAuth2 client credentials
	ClientSecret string
	//TokenURL: The token endpoint of the OAuth2 server, e.g., "https://oauth.example.com/oauth2/token"
	TokenURL string

	//SSLMinVersion is the minimum supported TLS version. Default is TLS 1.2.
	SSLMinVersion uint16
	Timeout       time.Duration

	//DefaultRetryCount is the default number of retries to perform with exponential backoff when
	//1. Clients receive 401 response from services
	//2. Clients' or services' connections to the OAuth2 server fails.
	//Default value is 5
	DefaultRetryCount int

	//CacheRoot is the root of the cache key for storing tokens in the cache.
	//The overall cache key will look like: <CacheRoot>/<cacheType>/<some key>
	//Default value is "sand"
	CacheRoot string

	Verbose bool
	// contains filtered or unexported fields
}

Client can be used to request token from an OAuth2 server

func NewClient

func NewClient(id, secret, tokenURL string) (client *Client, err error)

NewClient returns a Client with default option values. The default expiration time is set to 3599 seconds. The primary cache is in-memory. The secondary cache is nil with this client. Use NewClientWithCache if you want use the secondary cache.

func (*Client) OAuth2Token

func (c *Client) OAuth2Token(cacheKey string, scopes []string, option *tokenOption) (*oauth2.Token, error)

OAuth2Token returns an OAuth2 token retrieved from the OAuth2 server. It also puts the token in the cache up to specified amount of time.

func (*Client) OAuth2TokenWithoutCaching

func (c *Client) OAuth2TokenWithoutCaching(scopes []string, option *tokenOption) (token *oauth2.Token, err error)

OAuth2TokenWithoutCaching makes the connection to the OAuth server and returns oauth2.Token The returned token could have empty accessToken.

func (*Client) Request

func (c *Client) Request(cacheKey string, scopes []string, exec func(string) (*http.Response, error)) (*http.Response, error)

Request makes a service API request by first obtaining the access token from SAND. Then it deligates the token to the underlying function to make the service call. If the service returns 401, it performs exponential retry by requesting new tokens from SAND and make the service call. If the service returns 502, the service failed to connect to the authentication service and no retry will occur. Usage Example:

client.Request("some-service", []string{"s1", "s2"}, func(token string) (*http.Response, error) {
  // Make http request with "Bearer {token}" in the Authorization header
  // return the response and error
})

func (*Client) RequestWithCustomRetry

func (c *Client) RequestWithCustomRetry(cacheKey string, scopes []string, numRetry int, exec func(string) (*http.Response, error)) (*http.Response, error)

RequestWithCustomRetry allows specifying numRetry as the number of retries to use instead of the DefaultRetryCount, on a per-request basis. numRetry MUST be at least one so that if a client's token has expired, it can get a new token when retrying, at least once. Using a negative number for numRetry is equivalent to the "Request" function, which uses DefaultRetryCount. The retry durations are: 1, 2, 4, 8, 16,... seconds

func (*Client) Token

func (c *Client) Token(cacheKey string, scopes []string, option *tokenOption) (string, error)

Token returns an OAuth2 token string retrieved from the OAuth2 server. It also puts the token in the cache up to specified amount of time.

type ExpiredTokenError

type ExpiredTokenError struct {
	SandError
}

ExpiredTokenError when a client receives a token that is already expired

type InternalServerError

type InternalServerError struct {
	SandError
}

InternalServerError is returned when the client receives a 401 accessing the authentication service or the target service

type PermissionDeniedError

type PermissionDeniedError struct {
	SandError
}

PermissionDeniedError when service receives 403 from Sand while verifying a client token.

type SandError

type SandError struct {
	Message string `json:"message"`
}

func (SandError) Error

func (e SandError) Error() string

type Service

type Service struct {
	Client

	//The default resource name that this Service will check the token against.
	Resource string

	//Default context
	Context map[string]interface{}

	//The URL of the token verification endpoint, e.g., "https://oauth.example.com/warden/token/allowed"
	TokenVerifyURL string

	//The default expiry time for cache for invalid tokens and also valid tokens without expiry times
	//Default value is 3599 seconds
	//Only services need this because client tokens will always give expiry time
	DefaultExpDuration time.Duration

	//The scopes required for the service to access the token verification endpoint
	Scopes []string
}

Service can be used to verify a token with SAND

func NewService

func NewService(id, secret, tokenURL, resource, verifyURL string, scopes []string) (service *Service, err error)

NewService returns a Service struct.

func (*Service) CheckRequest

func (s *Service) CheckRequest(r *http.Request, targetScopes []string, action string) (map[string]interface{}, error)

CheckRequest checks the bearer token of an incoming HTTP request and return response with 'allowed' true/false field. If the error is of type sand.ConnectionError, the service should respond with HTTP status code 502. Otherwise the client would perform unnecessary retries. Example with Gin:

func(c *gin.Context) {
  response, err := sandService.CheckRequest(c.Request, []string{"scope1", "scope2"}, "action")
  if err != nil || response["allowed"] != true {
    c.JSON(sandService.ErrorCode(err), err)    //This would set 502 on ConnectionError
  }
}

func (*Service) CheckRequestWithCustomRetry

func (s *Service) CheckRequestWithCustomRetry(r *http.Request, targetScopes []string, action string, numRetry int) (map[string]interface{}, error)

CheckRequestWithCustomRetry allows specifying a positive number as number of retries to use instead of using DefaultRetryCount on a per-request basis. Using a negative number for numRetry is equivalent to the "Request" function

func (*Service) ErrorCode

func (s *Service) ErrorCode(err error) int

ErrorCode gets the HTTP error code based on the error type. By default it is 401 unauthorized; if the error is connection error, then it returns 502

func (*Service) VerifyRequest

func (s *Service) VerifyRequest(r *http.Request, opt *VerificationOption) (map[string]interface{}, error)

VerifyRequest takes the token in a request and verifies with SAND Remember to set a reasonable NumRetry value (>= 0) for the VerificationOption

func (*Service) VerifyTokenWithCache

func (s *Service) VerifyTokenWithCache(token string, opt *VerificationOption) (map[string]interface{}, error)

VerifyTokenWithCache tries to get the result for this token from the cache first. If not found in cache, if will make a token verification request with Sand.

type TokenWithExpiration

type TokenWithExpiration struct {
	Token             string
	ExpiresAtUnixNano int64
}

type TooManyRequestsError

type TooManyRequestsError struct {
	SandError
}

TooManyRequestsError when service receives 429 from Sand while verifying a client token.

type VerificationOption

type VerificationOption struct {
	TargetScopes    []string
	Resource        string
	Action          string
	Context         map[string]interface{}
	NumRetry        *int
	SkipCachedToken bool
	Timeout         time.Duration
}

VerificationOption affects how tokens are verified

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL