Skip to content

janekolszak/idp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

a4ef1ea · Jun 12, 2017
Sep 11, 2016
Sep 12, 2016
Sep 3, 2016
Apr 26, 2017
Sep 10, 2016
Jun 2, 2017
Jun 2, 2017
Sep 17, 2016
Jun 12, 2017
Jun 12, 2017
Jun 12, 2017

Repository files navigation

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
}