Authentication Setup with Kratos Hydra - UnlockedLabs/UnlockEdv2 GitHub Wiki
Oauth2/OIDC Authentication Setup for UnlockEdV2
UnlockEd uses Ory Hydra for an oauth2 server, and [Ory Kratos] for identity management.
The Ory REST API Documentation provides extensive resources about interacting with these services.
Because the Ory services are back-end only, we have integrated our own UI for user login, and oauth2 consent.
Oauth2 Clients (provider platforms, for which we need to authenticate our users into) are registered with us, and the information is stored
in our database. When adding a Provider Platform, there is an option to Auto Register
the provider, in which we have a provider specific setup that will be called using their API to add UnlockEd as an OIDC server/identity provider.
We handle the initial Oauth2 flow by redirecting our users as usual to the kratos browser endpoint: /self-service/login/browser
where kratos initializes a login flow for the user and sets the appropriate cookies. Kratos is configured to then redirect the
client to our /login
page with the ?flow={flow_uuidv4}
query parameter attached, and the CSRF token set in the cookie.
When it is an oauth2 flow, there is a &login_challenge=...
also appended to the URI.
When the user submits their login form, it is sent to our /api/login
endpoint, which then forwards the submission to kratos,
and attaches the necessary parameters to the POST /self-service/login
Kratos API endpoint, adding the oauth2 challenge if it's
found:
if form.Challenge != "" {
// if there was an oauth2 code challenge, we append it to the kratos url
url += "&login_challenge=" + form.Challenge
}
If the initial code challenge is successful, kratos will return a redirect_to
parameter in the JSON body, which we then
return to the client. If it is an Oauth2 flow, this will be configured to our /consent
endpoint, with the ?consent_challenge=...
query parameter included. If the user selects that they consent to log in to an external provider, they send us a POST request to
/api/consent/accept
with the challenge, and we submit this to Hydra's PUT /admin/oauth2/auth/requests/consent/accept
endpoint on behalf
of the user, with the appropriate request body that specifies information about the Oauth2 Token that will be built for the user.
func buildConsentBody(user *models.User) ([]byte, error) {
body := make(map[string]interface{})
body["remember"] = true
body["grant_access_token_audience"] = []string{os.Getenv("APP_URL")}
body["remember_for"] = 3600 * 24
body["grant_scope"] = []string{"openid", "offline", "profile", "email"}
body["session"] = map[string]interface{}{
"access_token": map[string]interface{}{
"scope": []string{"openid", "offline", "profile", "email"},
},
"id_token": map[string]string{
// we use the username for all these fields, because different platforms
// allow different field options by default, so we include a few just in case
"nickname": user.Username,
"email": user.Username,
"preferred_username": user.Username,
"locale": "en",
},
}
return json.Marshal(body)
}
Upon successful return, Hydra will include the final redirect_to
parameter, which we again forward on to the client.
redirectURI := response["redirect_to"].(string)
respBody := map[string]string{
"redirect_to": redirectURI,
}
return writeJsonResponse(w, http.StatusOK, respBody)
If the client is successfully registered with the correct parameters, this will be to the RedirectURI of the client. e.g.
canvas-url.com/login/oauth2/callback
and we will have a complete oauth2 login.