package callback

import "github.com/hashicorp/cap/oidc/callback"

callback is a package that provides callbacks (in the form of http.HandlerFunc) for handling OIDC provider responses to authorization code flow (with optional PKCE) and implicit flow authentication attempts.

Example

Code:

{
	// Create a new Config
	pc, err := oidc.NewConfig(
		"http://your-issuer.com/",
		"your_client_id",
		"your_client_secret",
		[]oidc.Alg{oidc.RS256},
		[]string{"http://your_redirect_url/auth-code-callback", "http://your_redirect_url/implicit-callback"},
	)
	if err != nil {
		// handle error
	}

	// Create a provider
	p, err := oidc.NewProvider(pc)
	if err != nil {
		// handle error
	}
	defer p.Done()

	// Create a Request for a user's authentication attempt that will use the
	// authorization code flow.  (See NewRequest(...) using the WithPKCE and
	// WithImplicit options for creating a Request that uses those flows.)
	ttl := 2 * time.Minute
	authCodeAttempt, err := oidc.NewRequest(ttl, "http://your_redirect_url/auth-code-callback")
	if err != nil {
		// handle error
	}

	// Create a Request for a user's authentication attempt using an implicit
	// flow.
	implicitAttempt, err := oidc.NewRequest(ttl, "http://your_redirect_url/implicit-callback")
	if err != nil {
		// handle error
	}

	// Create an authorization code flow callback
	// A function to handle successful attempts.
	successFn := func(
		state string,
		t oidc.Token,
		w http.ResponseWriter,
		req *http.Request,
	) {
		w.WriteHeader(http.StatusOK)
		printableToken := fmt.Sprintf("id_token: %s", string(t.IDToken()))
		_, _ = w.Write([]byte(printableToken))
	}
	// A function to handle errors and failed attempts.
	errorFn := func(
		state string,
		r *AuthenErrorResponse,
		e error,
		w http.ResponseWriter,
		req *http.Request,
	) {
		if e != nil {
			w.WriteHeader(http.StatusInternalServerError)
			_, _ = w.Write([]byte(e.Error()))
			return
		}
		w.WriteHeader(http.StatusUnauthorized)
	}
	// create the authorization code callback and register it for use.
	authCodeCallback, err := AuthCode(context.Background(), p, &SingleRequestReader{Request: authCodeAttempt}, successFn, errorFn)
	if err != nil {
		// handle error
	}
	http.HandleFunc("/auth-code-callback", authCodeCallback)

	// create an implicit flow callback and register it for use.
	implicitCallback, err := Implicit(context.Background(), p, &SingleRequestReader{Request: implicitAttempt}, successFn, errorFn)
	if err != nil {
		// handle error
	}
	http.HandleFunc("/implicit-callback", implicitCallback)
}

Index

Examples

Functions

func AuthCode

AuthCode creates an oidc authorization code callback handler which uses a RequestReader to read existing oidc.Request(s) via the request's oidc "state" parameter as a key for the lookup. In additional to the typical authorization code flow, it also handles the authorization code flow with PKCE.

The SuccessResponseFunc is used to create a response when callback is successful.

The ErrorResponseFunc is to create a response when the callback fails.

Example

Code:

{
	// Create a new Config
	pc, err := oidc.NewConfig(
		"http://your-issuer.com/",
		"your_client_id",
		"your_client_secret",
		[]oidc.Alg{oidc.RS256},
		[]string{"http://your_redirect_url/auth-code-callback"},
	)
	if err != nil {
		// handle error
	}

	// Create a provider
	p, err := oidc.NewProvider(pc)
	if err != nil {
		// handle error
	}
	defer p.Done()

	// Create a Request for a user's authentication attempt that will use the
	// authorization code flow.  (See NewRequest(...) using the WithPKCE and
	// WithImplicit options for creating a Request that uses those flows.)
	ttl := 2 * time.Minute
	authCodeAttempt, err := oidc.NewRequest(ttl, "http://your_redirect_url/auth-code-callback")
	if err != nil {
		// handle error
	}

	// Create an authorization code flow callback
	// A function to handle successful attempts.
	successFn := func(
		state string,
		t oidc.Token,
		w http.ResponseWriter,
		req *http.Request,
	) {
		w.WriteHeader(http.StatusOK)
		printableToken := fmt.Sprintf("id_token: %s", string(t.IDToken()))
		_, _ = w.Write([]byte(printableToken))
	}
	// A function to handle errors and failed attempts.
	errorFn := func(
		state string,
		r *AuthenErrorResponse,
		e error,
		w http.ResponseWriter,
		req *http.Request,
	) {
		if e != nil {
			w.WriteHeader(http.StatusInternalServerError)
			_, _ = w.Write([]byte(e.Error()))
			return
		}
		w.WriteHeader(http.StatusUnauthorized)
	}
	// create the authorization code callback and register it for use.
	authCodeCallback, err := AuthCode(context.Background(), p, &SingleRequestReader{Request: authCodeAttempt}, successFn, errorFn)
	if err != nil {
		// handle error
	}
	http.HandleFunc("/auth-code-callback", authCodeCallback)
}

func Implicit

Implicit creates an oidc implicit flow callback handler which uses a RequestReader to read existing oidc.Request(s) via the request's oidc "state" parameter as a key for the lookup.

It should be noted that if your OIDC provider supports PKCE, then use it over the implicit flow

The SuccessResponseFunc is used to create a response when callback is successful.

The ErrorResponseFunc is to create a response when the callback fails.

Example

Code:

{
	// Create a new Config
	pc, err := oidc.NewConfig(
		"http://your-issuer.com/",
		"your_client_id",
		"your_client_secret",
		[]oidc.Alg{oidc.RS256},
		[]string{"http://your_redirect_url/implicit-callback"},
	)
	if err != nil {
		// handle error
	}

	// Create a provider
	p, err := oidc.NewProvider(pc)
	if err != nil {
		// handle error
	}
	defer p.Done()

	// Create a Request for a user's authentication attempt using an implicit
	// flow.
	ttl := 2 * time.Minute
	implicitAttempt, err := oidc.NewRequest(ttl, "http://your_redirect_url/implicit-callback")
	if err != nil {
		// handle error
	}

	// Create an authorization code flow callback
	// A function to handle successful attempts.
	successFn := func(
		state string,
		t oidc.Token,
		w http.ResponseWriter,
		req *http.Request,
	) {
		w.WriteHeader(http.StatusOK)
		printableToken := fmt.Sprintf("id_token: %s", string(t.IDToken()))
		_, _ = w.Write([]byte(printableToken))
	}
	// A function to handle errors and failed attempts.
	errorFn := func(
		state string,
		r *AuthenErrorResponse,
		e error,
		w http.ResponseWriter,
		req *http.Request,
	) {
		if e != nil {
			w.WriteHeader(http.StatusInternalServerError)
			_, _ = w.Write([]byte(e.Error()))
			return
		}
		w.WriteHeader(http.StatusUnauthorized)
	}

	// create an implicit flow callback and register it for use.
	implicitCallback, err := Implicit(context.Background(), p, &SingleRequestReader{Request: implicitAttempt}, successFn, errorFn)
	if err != nil {
		// handle error
	}
	http.HandleFunc("/implicit-callback", implicitCallback)
}

Types

type AuthenErrorResponse

type AuthenErrorResponse struct {
	Error       string
	Description string
	Uri         string
}

AuthenErrorResponse represents Oauth2 error responses. See: https://openid.net/specs/openid-connect-core-1_0.html#AuthError

type ErrorResponseFunc

type ErrorResponseFunc func(state string, respErr *AuthenErrorResponse, e error, w http.ResponseWriter, req *http.Request)

ErrorResponseFunc is used by Callbacks to create a http response when the callback fails.

The function receives the state returned as part of the oidc authentication response. It also gets parameters for the oidc authentication error response and/or the callback error raised while processing the request. The function should use the http.ResponseWriter to send back whatever content (headers, html, JSON, etc) it wishes to the client that originated the oidc flow.

Just a reminder that the function parameters could also be used to update the oidc.Request for the request or log info about the request, if the implementation requires it.

type RequestReader

type RequestReader interface {
	// Read an existing Request entry.  The returned request's State()
	// must match the state used to look it up. Implementations must be
	// concurrently safe, which likely means returning a deep copy.
	Read(ctx context.Context, state string) (oidc.Request, error)
}

RequestReader defines an interface for finding and reading an oidc.Request

Implementations must be concurrently safe, since the reader will likely be used within a concurrent http.Handler

type SingleRequestReader

type SingleRequestReader struct {
	Request oidc.Request
}

SingleRequestReader implements the RequestReader interface for a single request. It is concurrently safe.

func (*SingleRequestReader) Read

func (sr *SingleRequestReader) Read(ctx context.Context, state string) (oidc.Request, error)

Read() will return it's single-request if the state matches it's Request.State(), otherwise it returns an error of oidc.ErrNotFound. It satisfies the RequestReader interface. Read() is concurrently safe.

type SuccessResponseFunc

type SuccessResponseFunc func(state string, t oidc.Token, w http.ResponseWriter, req *http.Request)

SuccessResponseFunc is used by Callbacks to create a http response when the callback is successful.

The function state parameter will contain the state that was returned as part of a successful oidc authentication response. The oidc.Token is the result of a successful token exchange with the provider. The function should use the http.ResponseWriter to send back whatever content (headers, html, JSON, etc) it wishes to the client that originated the oidc flow.

Just a reminder that the function parameters could also be used to update the oidc.Request for the request or log info about the request, if the implementation requires it.

Source Files

authcode.go docs.go implicit.go request_reader.go response_func.go testing.go

Version
v0.9.0 (latest)
Published
Feb 28, 2025
Platform
linux/amd64
Imports
8 packages
Last checked
2 months ago

Tools for package owners.