idp

package module
v0.9.0 Latest Latest
Warning

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

Go to latest
Published: Jun 12, 2017 License: MIT Imports: 13 Imported by: 0

README

Identity Provider (IdP) for Hydra

Build Status Code Climate GoDoc Gitter

This is a helper library for handling challenge requests from Hydra, it handles:

  • Storing challenge in a short lived cookie instead of query parameters
  • Passing user's consent to Hydra
  • Retriving keys from Hydra and using them for JWT verification
  • Caching keys and client info

IdP uses Gorilla sessions as the Store. There are many Gorilla sessions backend implementations out there.

About

Let's say we have an Identity Provider with:

  • /login endpoint that accepts Hydra's challenges
  • /consent endpoint that handles getting consent from the user

This is how challenge request could be hadled with the IdP library:

Sequence Diagram

Initialization

There are many implementations of Gorilla sessions. Let's use Postgres as the backend:

import (
	"github.com/janekolszak/idp"
	"github.com/antonlindstrom/pgstore"
	"time"
)

func main() {
	challengeCookieStore, err = pgstore.NewPGStore("postgres://user:pass@address/dbname", []byte("secret"))
	// Return on error

	// Create the IDP
	IDP := idp.NewIDP(&idp.IDPConfig{
		ClusterURL:            /* Hydra's address */,
		ClientID:              /* IDP's client ID */,
		ClientSecret:          /* IDP's client secret */,
		KeyCacheExpiration:    time.Duration(/* Key expiration time */) * time.Second,
		ClientCacheExpiration: time.Duration(/* Client info expiration */) * time.Second,
		CacheCleanupInterval:  time.Duration(/* Cache cleanup interval. Eg. 30 */) * time.Second,
		ChallengeExpiration:   time.Duration(/* Challenge cookie expiration. Eg. 10 */) * time.Minutes,
		ChallengeStore:        challengeCookieStore,
	})

	// Connects with Hydra and fills caches
	err = IDP.Connect(true /*TLS verification*/)
	// Return on error

}

Usage


func HandleChallengeGET(w http.ResponseWriter, r *http.Request) {
	// 0. Render HTML page with a login form
}

func HandleChallengePOST(w http.ResponseWriter, r *http.Request) {
	// 0. Parse and validate login data (username:password, login cookie etc)
	//    Return on error

	// 1. Verify user's credentials (eg. check username:password).
	//    Return on error
	//    Obtain userid

	// 2. Create a Challenge
	challenge, err := IDP.NewChallenge(r, userid)
	//    Return on error

	// 3. Save the Challenge to a cookie with a small TTL
	err = challenge.Save(w, r)
	//    Return on error

	// 4. Redirect to the consent endpoint
}

// Displays Consent screen. Here user agrees for listed scopes
func HandleConsentGET(w http.ResponseWriter, r *http.Request) {

	// 0. Get the Challenge from the cookie
	challenge, err := IDP.GetChallenge(r)
	//    Return on error

	// 1. Display consent screen
	//    Use challenge.User to get user's ID
	//    Use challenge.Scopes to display requested scopes

	// 2. If any error occured delete the Challenge cookie (optional)
	if err != nil {
		err = challenge.Delete(c.Writer, c.Request)
	}

	// 3. Render the HTML consent page
}

func HandleConsentPOST(w http.ResponseWriter, r *http.Request) {
	// 0. Get the Challenge from the cookie
	challenge, err := IDP.GetChallenge(c.Request)
	//    Return on error

	// 1. Parse and validate consent data (eg. form answer=y or list of scopes)
	//    Return on error

	// 2. If user refused access
	err = challenge.RefuseAccess(w, r)
	//    Return

	// 3. If userf agreed to grant access
	err = challenge.GrantAccessToAll(w, r)
	//    Return
}

Documentation

Overview

Package for handling challenge requests from Hydra(https://github.com/ory/hydra).

Index

Constants

View Source
const (
	VerifyPublicKey   = "VerifyPublic"
	ConsentPrivateKey = "ConsentPrivate"
)
View Source
const (
	SessionCookieName = "challenge"
)

Variables

View Source
var (
	ErrorBadPublicKey       = errors.New("cannot convert to public key")
	ErrorBadPrivateKey      = errors.New("cannot convert to private key")
	ErrorBadRequest         = errors.New("bad request")
	ErrorBadChallengeCookie = errors.New("bad format of the challenge cookie")
	ErrorChallengeExpired   = errors.New("challenge expired")
	ErrorNoSuchClient       = errors.New("there's no OIDC Client with such id")
	ErrorBadKey             = errors.New("bad key stored in the cache ")
	ErrorNotInCache         = errors.New("cache doesn't have the requested data")
	ErrorBadSigningMethod   = errors.New("bad signing method")
	ErrorInvalidToken       = errors.New("invalid token")
)

Functions

func ClientInfoKey

func ClientInfoKey(clientID string) string

Types

type Challenge

type Challenge struct {

	// Hydra's client
	Client *hclient.Client

	// Time of expiration
	Expires time.Time

	// Unique JWT id
	JTI string

	// Redirect URL
	Redirect string

	// Requested scopes
	Scopes []string

	// Set in the challenge endpoint, after authenticated.
	User string
	// contains filtered or unexported fields
}

func (*Challenge) Delete

func (c *Challenge) Delete(w http.ResponseWriter, r *http.Request) error

Deletes the challenge from the store

func (*Challenge) GrantAccessToAll

func (c *Challenge) GrantAccessToAll(w http.ResponseWriter, r *http.Request) error

User granted access to requested scopes, forward the desicion to Hydra via redirection.

func (*Challenge) RefuseAccess

func (c *Challenge) RefuseAccess(w http.ResponseWriter, r *http.Request) error

User refused access to requested scopes, forward the desicion to Hydra via redirection.

func (*Challenge) Save

func (c *Challenge) Save(w http.ResponseWriter, r *http.Request) error

Saves the Challenge to it's session store

func (*Challenge) Update added in v0.3.0

func (c *Challenge) Update(w http.ResponseWriter, r *http.Request) error

Update the Challenge, e.g. add user representation

type IDP

type IDP struct {
	// contains filtered or unexported fields
}

Identity Provider helper

func NewIDP

func NewIDP(config *IDPConfig) *IDP

Create the Identity Provider helper

func (*IDP) Close

func (idp *IDP) Close()

Closes connection to Hydra, cleans cache etc.

func (*IDP) Connect

func (idp *IDP) Connect(verifyTLS bool) error

Connect to Hydra

func (*IDP) GetChallenge

func (idp *IDP) GetChallenge(r *http.Request) (*Challenge, error)

Get the Challenge from a cookie, using Gorilla sessions

func (*IDP) NewChallenge

func (idp *IDP) NewChallenge(ctx context.Context, r *http.Request, user string) (challenge *Challenge, err error)

Create a new Challenge. The request will contain all the necessary information from Hydra, passed in the URL.

type IDPConfig

type IDPConfig struct {
	// Client id issued by Hydra
	ClientID string `yaml:"client_id"`

	// Client secret issued by Hydra
	ClientSecret string `yaml:"client_secret"`

	// Hydra's address
	ClusterURL string `yaml:"hydra_address"`

	// Expiration time of internal key cache
	KeyCacheExpiration time.Duration `yaml:"key_cache_expiration"`

	// Expiration time of internal clientid cache
	ClientCacheExpiration time.Duration `yaml:"client_cache_expiration"`

	// Internal cache cleanup interval
	CacheCleanupInterval time.Duration `yaml:"cache_cleanup_interval"`

	// Expiration time of internal clientid cache
	ChallengeExpiration time.Duration `yaml:"challenge_expiration"`

	// Gorilla sessions Store for storing the Challenge.
	ChallengeStore sessions.Store
}

Identity Provider's options

Jump to

Keyboard shortcuts

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