sand

package module
v1.4.0 Latest Latest
Warning

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

Go to latest
Published: Oct 2, 2020 License: MIT Imports: 15 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.MaxRetry      = 5       // Maximum number of retries on connection error
client.Cache         = nil     // A cache that conforms to the sand.Cache interface
client.CacheRoot     = "sand"  // A string as the root namespace in the cache

// 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"})

//Below shows the optional field (with the default value) that can be modified after a service is created
... // Same fields as client's above
service.DefaultExpTime = 3600,  # The default expiry time for cache for invalid tokens and also valid tokens which have no expiry times.

//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 := 3
  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

This section is empty.

Functions

func ExtractToken

func ExtractToken(authHeader string) string

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

Types

type AuthenticationError

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

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

func (AuthenticationError) Error

func (e AuthenticationError) Error() string

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

	//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
	Cache             cache.Cache

	//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
	// 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 3598 seconds. If you don't want to use a cache for some very convincing reason, you can set client's Cache to nil.

func NewClientWithCache added in v1.3.3

func NewClientWithCache(id, secret, tokenURL string, cache cache.Cache) (client *Client, err error)

NewClientWithCache returns a Client with default option values and a specified cache If you don't want to use a cache for some very convincing reason, you can set client's Cache to nil.

func NewClientWithExpiration added in v1.3.2

func NewClientWithExpiration(id, secret, tokenURL string, cacheExpiration time.Duration) (client *Client, err error)

NewClientWithExpiration returns a Client with default option values and specified expiration time on the cache. If you don't want to use a cache for some very convincing reason, you can set client's Cache to nil.

func (*Client) OAuth2Token added in v1.3.1

func (c *Client) OAuth2Token(cacheKey string, scopes []string, numRetry int) (*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 added in v1.3.1

func (c *Client) OAuth2TokenWithoutCaching(scopes []string, numRetry int) (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 added in v1.1.0

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, numRetry int) (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 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 3600 (1 hour)
	//Only services need this because client tokens will always give expiry time
	DefaultExpTime int

	//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 added in v1.1.0

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 added in v1.2.0

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 added in v1.2.0

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 VerificationOption added in v1.2.0

type VerificationOption struct {
	TargetScopes []string
	Resource     string
	Action       string
	Context      map[string]interface{}
	NumRetry     *int
}

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