Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: integration with Hydra #2549

Closed

Conversation

grantzvolsky
Copy link
Contributor

This PR adds the configuration option selfservice.hydra_admin_url. Setting this value to a Hydra admin endpoint URL turns Kratos into an OIDC identity provider. Resolves #273

Related issue(s)

#273

Checklist

  • I have read the contributing guidelines.
  • I have referenced an issue containing the design document if my change introduces a new feature.
  • I am following the contributing code guidelines.
  • I have read the security policy.
  • I confirm that this pull request does not address a security
    vulnerability. If this pull request addresses a security. vulnerability, I confirm that I got green light (please contact
    security@ory.sh) from the maintainers to push the changes.
  • I have added tests that prove my fix is effective or that my feature works.
  • I have added or changed the documentation.

Further Comments

@paulbalomiri
Copy link

Hi @grantzvolsky I'd like to give this a shot, against better advice from @vinckr. Which field is the login_challenge registration/login parameter taken from? I'm using dynamic registration, so I do have a full OIDC client registered with hydra, but without authentication.

@grantzvolsky
Copy link
Contributor Author

grantzvolsky commented Jul 11, 2022

Hi @paulbalomiri,

I would recommend waiting until this is reviewed and merged. There are a couple of things that still need to be addressed in this PR, like consent remember, making sure that all e2e tests pass, and other things.

To answer your question: when your app redirects the user agent to Hydra, Hydra generates a login_challenge and redirects to the login / consent UI with this challenge as a query parameter. Your login / consent UI is responsible for taking this parameter and passing it to Kratos. A very basic example, for testing purposes only, is here[1}.

[1}: https://github.com/ory/kratos/pull/2549/files#diff-7b487be0ee3e241929c9bd2006b53fa9fb3d7e550b822796ae5382cc429a98d1R77

@DzianisH
Copy link

Hi @grantzvolsky

Can I help you somehow to speed things up?

I wanna play around with Hydra and Kratos in my pet project. And it looks like it's doesn't make sense to implement any glue code for Hydra-Kratos integration as soon as native integration is almost there.

@aeneasr
Copy link
Member

aeneasr commented Jul 14, 2022

Hi, @grantzvolsky is currently busy with other priorities and will return to this topic once those are resolved! I know that this topic is urgent for many, but we can't clone him unfortunately ;)

@atreya2011
Copy link
Contributor

@grantzvolsky @aeneasr
Anyway I can help with this PR? 🙏🏼
It’s not urgent or anything but I would to help out in any way I can 🙇🏼

@atreya2011 atreya2011 mentioned this pull request Jul 23, 2022
6 tasks
@grantzvolsky grantzvolsky force-pushed the feature/kratos-hydra-integration branch from 8fc0546 to 2fb5c3c Compare August 14, 2022 03:12
@codecov
Copy link

codecov bot commented Aug 14, 2022

Codecov Report

Merging #2549 (cb55ccf) into master (84ea0cf) will decrease coverage by 0.05%.
The diff coverage is 73.44%.

❗ Current head cb55ccf differs from pull request most recent head d904106. Consider uploading reports for the commit d904106 to get more accurate results

@@            Coverage Diff             @@
##           master    #2549      +/-   ##
==========================================
- Coverage   75.84%   75.79%   -0.06%     
==========================================
  Files         302      304       +2     
  Lines       17796    17976     +180     
==========================================
+ Hits        13498    13625     +127     
- Misses       3264     3300      +36     
- Partials     1034     1051      +17     
Impacted Files Coverage Δ
selfservice/strategy/code/sender.go 80.76% <0.00%> (ø)
session/manager_http.go 73.18% <0.00%> (ø)
hydra/fake.go 44.44% <44.44%> (ø)
courier/email_templates.go 65.95% <60.00%> (ø)
driver/config/config.go 82.80% <63.63%> (-0.33%) ⬇️
selfservice/flow/login/handler.go 78.60% <64.81%> (-2.65%) ⬇️
selfservice/flow/login/hook.go 84.21% <65.00%> (-3.29%) ⬇️
selfservice/flow/registration/hook.go 79.13% <71.42%> (-0.50%) ⬇️
hydra/hydra.go 78.57% <78.57%> (ø)
selfservice/flow/registration/handler.go 80.39% <86.95%> (+1.00%) ⬆️
... and 12 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

@grantzvolsky grantzvolsky force-pushed the feature/kratos-hydra-integration branch from 9e3c934 to ef8859b Compare September 1, 2022 22:21
@grantzvolsky grantzvolsky force-pushed the feature/kratos-hydra-integration branch 2 times, most recently from a735d5b to 4267949 Compare September 13, 2022 03:53
@grantzvolsky grantzvolsky marked this pull request as ready for review September 13, 2022 04:00
selfservice/flow/login/flow.go Outdated Show resolved Hide resolved
internal/hydraclient/hydraclient.go Outdated Show resolved Hide resolved
Comment on lines 382 to 389
cr, err := hydraclient.AcceptHydraLoginRequest(
hydra_admin_url.String(),
hlc,
sess.IdentityID.String(),
h.d.Config().SessionPersistentCookie(r.Context()),
int64(h.d.Config().SessionLifespan(r.Context())/time.Second),
sess.AMR,
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless Ory Hydra tells us that it is ok to skip the login request:

			lr, err := hydraclient.GetHydraLoginRequest(hydra_admin_url, hlc)
			if err != nil {
			    // ...
			}
			if lr.Skip {
			    // ...
			} else {
			    // ...
			}

we should not auto-accept the login. Because it is possible that Ory Hydra is demanding us to show the login screen.

See this code here: https://github.com/ory/hydra-login-consent-node/blob/1496070c9bc5ddcefa327a7311d288c0ede60dcc/src/routes/login.ts#L26-L43

An important question here too is that the reference implementation uses the subject ( https://github.com/ory/hydra-login-consent-node/blob/1496070c9bc5ddcefa327a7311d288c0ede60dcc/src/routes/login.ts#L41 ) which is coming from Ory Hydra, not the own session store.

I think that Ory Hydra has a protection in place which would prevent us from Ory Hydra expecting one subject, and Ory Kratos sending another. But we should double-check that!

Long story short:

  1. You need to fetch the login request
  2. If skip is true, you can auto-accept it with the following requirements:
    1. No session exists yet -> the subject value from Ory Hydra's login request response is used. See example
    2. A session exists and kratos.identity.id == hydra.subject -> the identity id from the Ory Kratos session is used
    3. A session exists and kratos.identity.id != hydra.subject:
      • Either send kratos.identity.id to Ory Hydra, which most likely will end up in an error or;
      • Invalidate the session and continue to the login form
  3. If skip is false you need to:
    1. If a session exists, log the user out, and perform a fresh log in
    2. If a session does not exist, continue to the login form

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your insightful review!

Regarding the login logic, this is how I implemented it in the update that I just submitted:

  1. If skip is true, you can auto-accept it with the following requirements:
    i. No session exists yet -> the subject value from Ory Hydra's login request response is used. See example

I don't think this is ever the case, because we handle the Hydra challenge either if AlreadyLoggedIn (session exists) or in the post-login hook (session exists; it has just been created).

2.i and 2.ii

I believe the implementation satisfies both scenarios by always using kratos.identity.id. I just need to add a test for the error when there's a mismatch (will do over the weekend).

  1. If skip is false you need to:
    i. If a session exists, log the user out, and perform a fresh log in

I implemented this by adding a refresh=true to the login request initialization. This should have the added benefit that it won't "sign the user out of github when they don't complete a circleci login".

Regarding registration, no changes were necessary because the user always enters a password during registration, and the login challenge is handled in the post-registration hook.

Please let me know what you think :)

Copy link
Member

@aeneasr aeneasr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check the one long comment on login/handler.go

The same applies to the registration flow! Here too we need to respect the "skip" parameter

selfservice/flow/login/handler.go Outdated Show resolved Hide resolved
selfservice/flow/login/handler.go Outdated Show resolved Hide resolved
selfservice/flow/registration/flow.go Outdated Show resolved Hide resolved
selfservice/flow/registration/flow.go Outdated Show resolved Hide resolved
internal/hydraclient/hydraclient.go Outdated Show resolved Hide resolved
session/manager.go Show resolved Hide resolved
@@ -461,6 +514,26 @@
return
}

if ar.HydraLoginChallenge != uuid.Nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add tests for this new functionality in Go code.

if errors.Is(err, ErrAlreadyLoggedIn) {
if r.URL.Query().Has("login_challenge") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add tests for this new functionality in Go code.

@@ -354,6 +357,26 @@
return
}

if ar.HydraLoginChallenge != uuid.Nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add tests for this new functionality in Go code.

@@ -202,6 +205,25 @@
return nil
}

if a.HydraLoginChallenge != uuid.Nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add tests for this new functionality in Go code.

@aeneasr aeneasr force-pushed the feature/kratos-hydra-integration branch from 473a6a6 to 0cce52f Compare September 14, 2022 08:44
import * as uuid from 'uuid'
import * as oauth2 from '../../../helpers/oauth2'

context('OpenID Provider', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good set up in the tests :)

@aeneasr aeneasr force-pushed the feature/kratos-hydra-integration branch from 0cce52f to 82bb054 Compare September 14, 2022 08:58
@grantzvolsky grantzvolsky force-pushed the feature/kratos-hydra-integration branch from 5245cc2 to 7ce1a88 Compare October 2, 2022 22:31
@jpollard-cs
Copy link

jpollard-cs commented Oct 4, 2022

@grantzvolsky I don't want to step on your toes here, but would you find it helpful if I fixed some of the breaking tests or are there more breaking changes to come such that it doesn't make sense to do that yet?

@grantzvolsky
Copy link
Contributor Author

@jpollard-cs Do you mean the e2e tests? They should pass as soon as ory/kratos-selfservice-ui-node#198 is merged.

Regarding the tests that Aeneas requested to be added, the only tests that are missing at the moment are those for the registration handler and registration hook, and I expect to add those tomorrow.

@jpollard-cs
Copy link

ory/kratos-selfservice-ui-node#198

amazing! let me know if there's any other way I can help

@@ -414,6 +414,7 @@ func TestCompleteLogin(t *testing.T) {

t.Run("case=succeeds with passwordless login", func(t *testing.T) {
run := func(t *testing.T, spa bool) {
conf.MustSet(ctx, config.ViperKeySessionWhoAmIAAL, "aal1")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aeneasr Please take a look at why this test passes in master without this configured.

Copy link
Member

@aeneasr aeneasr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've left a few comments on things I noticed while going through the code.

I'll now take a look at the integration e2e!

hydra/hydra.go Outdated Show resolved Hide resolved
hydra/hydra.go Show resolved Hide resolved
hydra/hydra.go Show resolved Hide resolved
selfservice/flow/login/handler.go Show resolved Hide resolved
selfservice/flow/login/handler.go Outdated Show resolved Hide resolved
selfservice/flow/login/hook.go Outdated Show resolved Hide resolved
selfservice/flow/registration/handler.go Show resolved Hide resolved
@@ -354,6 +366,16 @@ func (h *Handler) fetchFlow(w http.ResponseWriter, r *http.Request, ps httproute
return
}

if ar.HydraLoginChallenge.Valid {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add tests for this in handler_test.go

If you don't want to boot hydra, you could also use a gomock implementation of the hydra client

selfservice/flow/login/handler.go Show resolved Hide resolved
@@ -0,0 +1,356 @@
package password_test
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe move this file to the hydra directory?

@grantzvolsky grantzvolsky force-pushed the feature/kratos-hydra-integration branch from 38f4e8f to ec3befb Compare October 13, 2022 02:32
@grantzvolsky grantzvolsky force-pushed the feature/kratos-hydra-integration branch from 195815e to 78ddde8 Compare October 13, 2022 09:40
@grantzvolsky grantzvolsky force-pushed the feature/kratos-hydra-integration branch from 7d7fb95 to 3252529 Compare October 13, 2022 11:07
@mitar
Copy link
Contributor

mitar commented Oct 26, 2022

This is made obsolete by #2804?

@aeneasr
Copy link
Member

aeneasr commented Oct 26, 2022

yes

@aeneasr aeneasr closed this Oct 26, 2022
@DzianisH
Copy link

@grantzvolsky you are my hero, thank you =)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement Hydra integration
7 participants