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.
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:
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
}
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
}