ssokenizer

package module
v0.0.0-...-0ba283a Latest Latest
Warning

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

Go to latest
Published: Dec 12, 2025 License: Apache-2.0 Imports: 14 Imported by: 0

README

ssokenizer

Ssokenizer provides a layer of abstraction for applications wanting to authenticate users and access 3rd party APIs via OAuth, but not wanting to directly handle users' API tokens. Ssokenizer is responsible for performing the OAuth dance, obtaining the user's OAuth access token. The token is then encrypted for use with the tokenizer HTTP proxy. By delegating OAuth authentication to ssokenizer and access token usage to tokenizer, applications limit the risk of tokens being lost, stolen, or misused.

Configuration

Ssokenizer searches for a configuration file at /etc/ssokenizer.yml, but a path can be specified with the -config flag. See /etc/ssokenizer.yml in this repo for an annotated example configuration. Environment variables in the configuration file are expanded when the application is booting.

Deploying to fly.io

  • Fork this repository on GitHub
  • Clone the forked repository
  • In a terminal, enter the repository directory and run fly launch
  • Follow the prompts, declining to deploy the new application.
  • Set secrets (TOKENIZER_SEAL_KEY, PROXY_AUTH, <PROVIDER>_CLIENT_SECRET) by running fly secrets set --stage SECRET_NAME="SECRET_VALUE"
  • Edit the configuration file in /etc/ssokenizer.yml to reflect the OAuth providers you would like to support. The secrets you set in the previous step will be available as environment variables in this configuration file.
  • Run make deploy or fly deploy

Usage

To start the authentication flow, navigate users to https://<ssokenizer-url>/<provider-name>/start?state=<state>. The <ssokenizer-url> and <provider-name> will depend on your configuration and how you've deployed the app. The state parameter is used to prevent login-CSRF attacks. Your application should generate a random string and associate it with the user's session by either putting it in the session-store provided by your web framework or by putting it directly in a cookie.

The user will now perform the OAuth dance with ssokenizer and the identity provider. Upon successful completion, the user will be redirected back to your configured return_url with several parameters:

  • sealed - The sealed OAuth access token and refresh token (if applicable), ready for use with your tokenizer deployment.
  • expires - The unix epoch time when the access token will expire, if applicable.
  • state - The state parameter that you passed to the /start URL. It is important for your application to verify that this matches the state value you stored in the user's session or cookie.

If the OAuth dance doesn't finish successfully, an error parameter will be added to the configured return_url instead of the sealed and expired parameter.

Using the sealed token

You are now ready to communicate with the provider API using the sealed token via the tokenizer HTTP proxy. You'll need to send the sealed token in the Proxy-Tokenizer header. You'll need to send the configured proxy_authorization secret in the Proxy-Authorization with the Bearer authorization scheme. Remember that requests made via tokenizer must use HTTP instead of HTTPS.

The following demonstrates how you might call the Google "userinfo" endpoint using cURL:

curl \
    -x $TOKENIZER_URL \
    -H "Proxy-Authorization: Bearer $PROXY_AUTH" \
    -H "Proxy-Tokenizer: $SEALED_TOKEN" \
    http://openidconnect.googleapis.com/v1/userinfo
Refreshing access tokens

Some identity providers issue access tokens that expire quickly along with refresh tokens that can be used to fetch new access tokens. To fetch a new access token, send a request to https://<ssokenizer-url>/<provider-name>/refresh via tokenizer. Include the sealed token in the Proxy-Tokenizer header with JSON-formatted parameters after a semicolon. Use {"st":"refresh"} to instruct the tokenizer to extract the refresh token instead of the access token. The response body will contain the new token, sealed for use with tokenizer and the Cache-Control header will contain the seconds until the token expires.

The following demonstrates how you might refresh a token using cURL:

curl \
    -x $TOKENIZER_URL \
    -H "Proxy-Authorization: Bearer $PROXY_AUTH" \
    -H "Proxy-Tokenizer: $SEALED_TOKEN; {\"st\":\"refresh\"}" \
    http://$SSOKENIZER_HOSTNAME/$PROVIDER_NAME/refresh

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrProviderNotFound = errors.New("provider not found")

Functions

func GetLog

func GetLog(r *http.Request) logrus.FieldLogger

Gets the logrus.FieldLogger from the context. Requests are logged by Transaction.ReturnData/ReturnError.

func SaveTransaction

func SaveTransaction(w http.ResponseWriter, r *http.Request, t *Transaction) error

SaveTransaction updates an existing transaction cookie.

func WithError

func WithError(r *http.Request, err error) *http.Request

Updates the logrus.FieldLogger in the context with "error" field. Requests are logged by Transaction.ReturnData/ReturnError.

func WithField

func WithField(r *http.Request, key string, value any) *http.Request

Updates the logrus.FieldLogger in the context with added field. Requests are logged by Transaction.ReturnData/ReturnError.

func WithFields

func WithFields(r *http.Request, fields logrus.Fields) *http.Request

Updates the logrus.FieldLogger in the context with added fields. Requests are logged by Transaction.ReturnData/ReturnError.

func WithLog

func WithLog(r *http.Request, l logrus.FieldLogger) *http.Request

Updates the logrus.FieldLogger in the context with added data. Requests are logged by Transaction.ReturnData/ReturnError.

func WithProvider

func WithProvider(r *http.Request, p Provider) *http.Request

Types

type Provider

type Provider interface {
	http.Handler
	Validate() error
	PC() *ProviderConfig
}

func GetProvider

func GetProvider(r *http.Request) Provider

type ProviderConfig

type ProviderConfig struct {
	Tokenizer TokenizerConfig

	// URL is the full URL where this provider is served from.
	URL url.URL

	// ReturnURL is the URL that the provider should redirect to after
	// authenticating the user.
	ReturnURL url.URL
}

func (*ProviderConfig) Validate

func (p *ProviderConfig) Validate() error

type ProviderRegistry

type ProviderRegistry interface {
	Get(ctx context.Context, name string) (Provider, error)
}

type Server

type Server struct {
	// Address is populated with the listening address after [Start] is called.
	Address string

	// Done is closed when the server has stopped. It is not populated until
	// [Start] is called.
	Done chan struct{}

	// Err is populated with any error returned by the HTTP server. It should
	// not be read until Done is closed.
	Err error
	// contains filtered or unexported fields
}

func NewServer

func NewServer(providers ProviderRegistry) *Server

Returns a new Server.

func (*Server) ServeHTTP

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)

func (*Server) Shutdown

func (s *Server) Shutdown(ctx context.Context) error

Gracefully shut down the server. If the context is cancelled before the shutdown completes, the server will be shutdown immediately.

func (*Server) Start

func (s *Server) Start(address string) error

Start the server in a goroutine, listening at the specified address (host:port).

type StaticProviderRegistry

type StaticProviderRegistry map[string]Provider

func (StaticProviderRegistry) Get

type TokenizerConfig

type TokenizerConfig struct {
	// SealKey is the key we encrypt tokens to.
	SealKey string

	// Auth specifies the auth requires to use the sealed token.
	Auth tokenizer.AuthConfig

	// RequestValidators specifies validations that tokenizer should run on
	// requests before unsealing/adding token. Eg. limit what hosts the token
	// can be sent to.
	RequestValidators []tokenizer.RequestValidator
}

func (*TokenizerConfig) SealedSecret

func (t *TokenizerConfig) SealedSecret(processor tokenizer.ProcessorConfig) (string, error)

func (*TokenizerConfig) Validate

func (t *TokenizerConfig) Validate() error

type Transaction

type Transaction struct {
	// Random state string that will be returned in our redirect to the relying
	//  party. This is used to prevent login-CSRF attacks.
	ReturnState string

	// Random string that provider implementations can use as the state
	// parameter for downstream SSO flows.
	Nonce string

	// Time after which this transaction cookie will be ignored.
	Expiry time.Time

	// Parameters forwarded from the start request that should also be sent
	// to the token exchange request (e.g., source_id for Vanta).
	ForwardedParams map[string]string
}

State about the user's SSO attempt that is stored as a cookie. Cookies are set with per-provider paths to prevent transactions from different providers from interfering with each other.

func RestoreTransaction

func RestoreTransaction(w http.ResponseWriter, r *http.Request) *Transaction

func StartTransaction

func StartTransaction(w http.ResponseWriter, r *http.Request) *Transaction

func (*Transaction) ReturnData

func (t *Transaction) ReturnData(w http.ResponseWriter, r *http.Request, data map[string]string)

Return the user to the returnURL with the provided data set as query string parameters.

func (*Transaction) ReturnError

func (t *Transaction) ReturnError(w http.ResponseWriter, r *http.Request, msg string)

Return the user to the returnURL with the provided msg set in the `error` query string parameter.

Directories

Path Synopsis
cmd
ssokenizer command

Jump to

Keyboard shortcuts

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