Yes, that’s right. We should be able to exchange j...
# contributors
c
Yes, that’s right. We should be able to exchange jwt assertion for access token. Client authentification should be optional in this case, I would like to force only client_id parameter, but that’s all.
@high-optician-2097 so my plan will be something like that: 1. access_request_handler.go: NewAccessRequest i will delete code that authenticates client 2. On Handlers i will add 2 methods: CanHandlerRequest and IsClientAuthRequired 3. So we can iterate through token endpoints and if CanHandleRequest return true, we can pass request to this handler (this also will free us from type asserting error in order to check if handler is suitable), but before that we call IsClientAuthRequired, if true then we authenticate client and set it to accessRequest, otherwise not
and this will be breaking changes certainly for version
h
I don’t like this plan to be honest - it removes a ton of safe guards by circumventing this whole process. Can we think of another idea?
c
Why do you think it will remove safeguards? On current handlers IsClientAuthRequired will always return true. So i think it wont change anything in current flow, only will add ability to override it in new handler. If i am wrong correct me plz
h
ah maybe I misunderstood what you wrote - could you maybe write down a quick and dirty overview in go of how this would work?
c
sure
Current simplified code:
Copy code
func (f *Fosite) NewAccessRequest(ctx context.Context, r *http.Request, session Session) (AccessRequester, error) {
   client, err := f.AuthenticateClient(ctx, r, r.PostForm)
   if err != nil {
      return accessRequest, err
   }
   accessRequest.Client = client

   var found = false
   for _, loader := range f.TokenEndpointHandlers {
      if err := loader.HandleTokenEndpointRequest(ctx, accessRequest); err == nil {
         found = true
      } else if <http://errors.Is|errors.Is>(err, ErrUnknownRequest) {
         // do nothing
      } else if err != nil {
         return accessRequest, err
      }
   }

   if !found {
      return nil, errorsx.WithStack(ErrInvalidRequest)
   }
   return accessRequest, nil
}
After my suggested changes
Copy code
func (f *Fosite) NewAccessRequest(ctx context.Context, r *http.Request, session Session) (AccessRequester, error) {
   for _, loader := range f.TokenEndpointHandlers {
      if !loader.CanHandlerRequest(ctx, accessRequest) {
         continue   
      }  
      if loader.IsClientAuthRequired() {
         client, err := f.AuthenticateClient(ctx, r, r.PostForm)
         if err != nil {
            return accessRequest, err
         }
         accessRequest.Client = client
      }  
      if err := loader.HandleTokenEndpointRequest(ctx, accessRequest); err == nil {
         return accessRequest, err
      }
      return accessRequest, nil
   }

   return nil, errorsx.WithStack(ErrInvalidRequest)
}
Alternative: We can implement a new method instead of NewAccessRequest, something like NewJwtAccessRequest, but i don't like it personally. However it will free us from touching current handlers. But i think it will be hard for Hydra to use it.
h
ok yeah I think that makes more sense the way you described it!
I would however revert the logic to
loader.CanSkipClientAuth
!
c
Thanks) So is first approach is ok for you in general?
"loader.CanSkipClientAuth" - no problem
just curios: why you want to revert logic to loader.CanSkipClientAuth?
h
yes the approach looks good 🙂 sorry I was at lunch
so I want the default (false) to have client auth
and the non default (true) to skip it
this makes it a bit easier in case you forget to set the value somewhere or something
c
ahh, i get it, thanks
Can you plz help me with this part
Copy code
for _, loader := range f.TokenEndpointHandlers {
      if err := loader.HandleTokenEndpointRequest(ctx, accessRequest); err == nil {
         found = true
      } else if <http://errors.Is|errors.Is>(err, ErrUnknownRequest) {
         // do nothing
      } else if err != nil {
         return accessRequest, err
      }
   }
In original code i see, that even if some token endpoint is executed, we still continue to iterate over all endpoints, so it is possible to execute two or more endpoints at the same time. In oauth 2.0 flow it does not make sense. I think only one endpoint should be executed per request, but maybe i am missing some point. Should i preserve this logic or no?
@high-optician-2097 Ok, i looked up code myself and looks like this logic should be preserved, because Open ID Connect Explicit Token handler relies on it
@high-optician-2097 Hello! I would like to discuss with you interface for retrieving public keys, to check signature of auth grant assertion. I am thinking about this interface
Copy code
type JWTAuthGrantStorage interface {
   GetPublicKey(ctx context.Context, issuer string, subject string, keyId string) (interface{}, error)
   GetPublicKeys(ctx context.Context, issuer string, subject string) ([]interface{}, error)
   GetPublicKeyScopes(ctx context.Context, issuer string, subject string, keyId string) ([]string, error)
}
What do you think?
h
What would
GetPublicKeyScopes
be used for?
c
We can see here https://tools.ietf.org/html/rfc7523#section-2.1, then when we requesting token endpoint, we can pass scopes, so we need mechanism to check requested scoped againts granted scopes, which i think we can attach to public key
h
So a public key gets a set of granted scopes basically?
c
Basically yes, however maybe it is more correct to attach scopes not to public key, but to some GrantedAssertion, and store this assertion, so when we receive assertion from client, we can load granted assertion and compare it with assertion from client.
but then we will need some kind of identifier for this assertion. So i decided to attach scopes to public key
h
hm yeah i think that makes sense, to attach it to the public key
c
I have one more question) Can we extend this interface?
Copy code
type Session interface {
   // SetExpiresAt sets the expiration time of a token.
   //
   //  session.SetExpiresAt(fosite.AccessToken, time.Now().UTC().Add(time.Hour))
   SetExpiresAt(key TokenType, exp time.Time)

   // GetExpiresAt returns the expiration time of a token if set, or time.IsZero() if not.
   //
   //  session.GetExpiresAt(fosite.AccessToken)
   GetExpiresAt(key TokenType) time.Time

   // GetUsername returns the username, if set. This is optional and only used during token introspection.
   GetUsername() string

   // GetSubject returns the subject, if set. This is optional and only used during token introspection.
   GetSubject() string

   // Clone clones the session.
   Clone() Session
}
I need method to set subject, but interface don't have it.
h
hm you could add another interface and do type assertion!
c
and if supplied session wont implement my new interface, what should i do? Because in auth grant jwt it is need to set subject in session
and why current interface missing setters for subject and username in the first place?
h
then the jwt grant doesn’t work 🙂
😣 1
😅 1
becuase they are not used anywhere in fosite
c
ok, i guess i will add another interface then
thanks! 😊
While we were developing jwt auth grant, we faced one problem. Oauth2 Client that is used in integration tests, has support for jwt auth grant, but this clients assumes, that scope parameter in inside assertion, as a jwt claim (which goeas againts rfc). We open issue here, you can see details https://github.com/golang/oauth2/issues/461 But for now we must choose the way) We have diffrent options : 1. write own client just for jwt test 2. do not test scope for now in integration test 3. add support for scope in assertion, but we still can't test scope as post parameter then @high-optician-2097 what do you think? because we would like to open PR with supporting jwt auth grant and integration test is the last issue, we must solve.
h
We definitely need to test the scope! So we need to write our own client for the test! I also checked and it seems that you are correct and that this isn’t implemented in the oauth2 library of golang yet
c
Ok, thanks, we also preferred the first variant more)
Also can we start here to discuss how to add support for auth grant jwt in Hydra? Mostly from hydra we need to implement this interface
Copy code
type JWTAuthGrantStorage interface {
   // GetPublicKey returns public key, issued by 'issuer', and assigned for subject. Public key is used to check
   // signature of jwt assertion in authorization grants.
   GetPublicKey(ctx context.Context, issuer string, subject string, keyId string) (*jose.JSONWebKey, error)

   // GetPublicKeys returns public key, set issued by 'issuer', and assigned for subject.
   GetPublicKeys(ctx context.Context, issuer string, subject string) (*jose.JSONWebKeySet, error)

   // GetPublicKeyScopes returns assigned scope for assertion, identified by public key, issued by 'issuer'.
   GetPublicKeyScopes(ctx context.Context, issuer string, subject string, keyId string) ([]string, error)
}
I was thinking about two variants: 1. we can use existing API to add public key, but create a new endpoint to add relation between public key and its issuer and subject 2. Or we can create new endpoint to add both public key and it's issuer and subject in one request
h
Thank you @clean-mouse-35824 - I think using the existing API makes more sense - what do you think?
c
I have rethink my previous two variants and now there are more concrete: 1. At first step we add public key via already existing endpoints /keys/{set} (however we will need first let hydra generate key itself and only then we can update it with already existing jwk, if i read api manual correctly). Step two: we can add new endpoint POST /issuers/{issuer} to bind public key created in first step to issuer and subject. 2. In second variant we can bind and create public key in one request to POST /issuers/{issuer}. I like it more, because we can reduce number of request. What do you think?
and yes, forgot to mention, in both variants we still can manage keys through /keys endpoints. E.x delete key will result in deleting binding also
h
What would the
issuers
API do? Does it set the scope and subject of the resulting access token?
We only need the public key in ORY Hydra correct?
So to be honest - I think we should not do this via the HTTP API. I think this key will not change often, correct? There will also not be hundreds of thousands of keys I think - more like a few?
Also, I thought the “subject” value would be variable - meaning that I can exchange JWT with sub “foo” and “bar” and “baz” for their respective access tokens without any additional allow list
c
Basically we need to verify incoming assertion. To do it we need public key to check signature. But we also need to verify issuer claim in assertion and subject. And check scopes too. So i thought
Issuers
api will let us create trusted issuers with their public keys. Basically bind public key to issuer, subject and scopes.
"We only need the public key in ORY Hydra correct?" - only public keys, private is outside of hydra.
"So to be honest - I think we should not do this via the HTTP API. I think this key will not change often, correct? There will also not be hundreds of thousands of keys I think - more like a few?" - If you mean configuration it is not very suited, because main case for auth grant via jwt is service accounts. And we must be able to create them on the fly, so we need to add new public keys to Hydra using api, because service accounts can be created through API as well.
"Also, I thought the “subject” value would be variable - meaning that I can exchange JWT with sub “foo” and “bar” and “baz” for their respective access tokens without any additional allow list" - RFC recommends to check them. The only way is to bind public key to subject also, however we can make it optional. You can bind or not key to subject.
h
Right, that makes a lot of sense @clean-mouse-35824
So yes, I think a dedicated API would be good. However, before implementing it in ORY Hydra, could you maybe create an issue or a PR with the planned API layout? We can then go over it and make it the best API we possibly can 🙂
c
Yes, sure)
How you would like to see API layout, in what form?
h
doesn’t have to be formal, a normal markdown issue is enough
just like “GET /path” with parameters “asdf”, “bfdsa”, …
c
Ok!
@high-optician-2097 Here it is https://github.com/ory/hydra/issues/2229
h
thank you, that is wonderful - i will give feedback by eob
c
Thank you very much, will be waiting)
@high-optician-2097 here goes fosite PR for adding jwt grant. Hydra in process)
h
thank you! i have time today to review it
c
@high-optician-2097 Hi again! How are you, fine? I need your help) So looking back at our grants https://github.com/ory/hydra/issues/2229 i am wondering how to delete expired grants. Looking up the code in Hydra code, i see that there are two ways, that is currently used: 1) delete something expired in the same transaction, where new something is inserted (looking at SetClientAssertionJWT method in persister) or by having a dedicated handler (like FlushHandler), which external client must call with some periodicity. What is better way for grants, what you think?
h
Hey there, glad to have you back. Leaving the office right now, will ping you tomorrow!
c
thanks) i also pushed changes into fosite pr, when you have time have a look
@high-optician-2097 still waiting your ping)
h
hey there, sorry, i was super busy - we’re building our new kitchen this week
🙂
c
congratulations!
Is it office or home?
h
office 🙂
c
looks lovely)
@high-optician-2097 just reminding about my question 😃 "Hi again! How are you, fine? I need your help) So looking back at our grants https://github.com/ory/hydra/issues/2229 i am wondering how to delete expired grants. Looking up the code in Hydra code, i see that there are two ways, that is currently used: 1) delete something expired in the same transaction, where new  something is inserted (looking at SetClientAssertionJWT method in persister) 2) or by having a dedicated handler (like FlushHandler), which external client must call with some periodicity. What is better way for grants, what you think?"
h
sorry again… I think this should be part of the flush handler
c
@high-optician-2097 Hi! Are you still up to review PR for fosite?) this one i mean https://github.com/ory/fosite/pull/546
h
Oh man, I am so sorry. My schedule is just insane at this time. I won’t be able to do it today, but I have set a calendar entry for tomorrow.
c
Sounds great! I just always afraid that you forgot about it) And i really want to push it till the end.
h
No you’re absolutely right - you can punch me. It’s on my list to push through but I sometimes get lost in the amount of todos, and the PR takes quite a while to review (~1 hour) due to it’s security implications, and I need to do it in the morning when I’m fresh!
c
definitely it needs a lot time and cautious cause of security subject!
h
Actually, tomorrow will not be possible, but I’ve blocked friday 10-11am. If I find minor code things I’ll just push on your branch. Hopefully we can get it merged on friday!
c
i am still up to make changes too, so it is up to you, i can do minor things also or you can do it. Looking forward for friday then)
h
proof
c
🤣
so nice !
h
and sorry again, I hate it when maintainers do that!
c
do what?)
making themselves target for punching? 😂
This black squares is so terrifying actually!
h
haha, no, saying they’ll do something and then don’t follow up 😅
haha yeah..
@clean-mouse-35824 as promised, looking into it now
c
Hi! Thank you very much!
h
Good fucking job, I love the use of testify/suite
@clean-mouse-35824 can you please enable write access for maintainers?
in the pr
c
yes
where this option?
c
for some reason i dont see this checkbox
h
but doesn’t matter I can solve it differently
c
no i am not
thats why i dont have this checkbox(
h
makes sense - https://github.com/ory/fosite/pull/560 < your changes
the ci is a bit difficult..let’s see if it passes now
c
i hate ci )), i like to write code)
h
ci is good as long as it works 🤣
c
i notice also that in your new pr there is an issue with "*Semantic Pull Request* — add a semantic PR title"
h
yeah that’s ok
@clean-mouse-35824 had to resolve some regression issues in the tests but it’s all done now
@magnificent-energy-493 please make sure to put vladimir on the mailout list 🙂
👌 1
released as 0.37 - thank you for your hard work!
c
Great news! 🎉🎉🎉
Please include this guy too
👍 1
Ok, it is now time to open PR for Hydra after some tweaks )))
h
:))))
c
@high-optician-2097 here you go) https://github.com/ory/hydra/pull/2384
h
Ah, awesome! I’m currently on vacation but will be back on monday!
c
Have a good rest there!
h
thank you 🙂
c
@high-optician-2097 Hi! Wanted to ask what is mailout list actually? 😂 (i am slowpoke)
h
@magnificent-energy-493
c
@high-optician-2097 Hi again) I am writing test for jwtbearer handler, how i can regenerate internal httpclient? So i can use it to make requests to new handler? I tried
Make sdk
- but it changes a lot of files 🤯
h
make sdk is the correct command! even if it touches a lot of files :)
c
ok)
@high-optician-2097 Hi! Will you able to look at https://github.com/ory/hydra/pull/2384 ?
h
ah yes, you can request review from me and mark as “not draft” then it shows up in my to do list 🙂
c
ahh, this is how it works 😃
@high-optician-2097 Hi! I noticed that
make sdk
no longer works in Makefile in Hydra, because this file
bash <(curl <https://raw.githubusercontent.com/ory/cli/master/install.sh>) -b .bin v0.0.72
looks like no longer exists, how can make to work it again?
h
i changed that yesterday sry
that works
c
ok, thanks!
@high-optician-2097 can you please review this https://github.com/ory/hydra/pull/2384 if you have time? we really want it to be merged 😃. Everything is done there
@high-optician-2097 Hi again! I think this PR https://github.com/ory/hydra/pull/2384 is ready for you 😃
h
Wohoo! Awesome, I will need to block some time during the holidays as there are several large scale changes for hydra and kratos 🙂 i will try to merge them all that week
c
Thanks!
@high-optician-2097 Is it possible to receive Christmas gift from you in the form of this PR merged https://github.com/ory/hydra/pull/2384? 😋
Would be wonderful if we can merge it before 2022 Year
h
It's on my PR short list. I'm not working the next days but have a bit of time before NYE
c
Thank you!