Closed tdhsmith closed 6 years ago
@tdhsmith Thanks for pulling this together. It's been on my mind for a while too.. it always felt wrong generating the claim values in the Factory class, and I've tried a few things myself, but hadn't come up with anything workable.
You have definitely peaked my interest on the matter again, so I will look at trying some stuff out soon!
As you say though, there are some things that will need consideration, and we'll need to be careful not to introduce too much complexity. Nonetheless, there should be some good wins to be made with this
Cheers!
Just to let you know, I'm working on refactoring the whole validation system. It's coming along nicely!
Can't wait to share what I have 😄
I've been thinking it might be cleaner and more modular to hold all of the validation methods and defaults inside each of the
Claim
classes. This would make claim initialization and validation a lot more configurable, and checks would be more internalized so the user doesn't have to manually examine the token payload.Before I go too far, I want to highlight a couple use cases:
iss
verify that it matches the current domain as a sanity check. (Really this shouldn't come up in practice because different issuers would normally have different keys, but it's possible some people want multiple token issuing services all running on the same Laravel instance.)csrf
claim to help mitigate request forgery when storing JWTs in cookies. A couple of existing services already use a claim for this (Stormpath has anxsrfToken
claim and OpenID Connect has anonce
claim). I could certainly do this already, but it would be nice if I didn't have to expose the JWT internals to do it.iat
) or having a finite number of refreshes (we could have arefresh_count
claim that tracked this). As it stands these would require a lot of conditional code inside the manager/validator/factory and extra config values, but with claim handling it would just take a new claim class.Here's an approximate structure as an example:
Example existing claim:
Example new claim:
Not shown: the major changes to manager/payload/factory/validator, but hopefully you can see some faint sketches of how it would work based on the interface above. Might need a
ClaimManager
go-between so the settings aren't duplicated everywhere, but I'm not sure if that's necessary.There are some challenges I'm already apprehending:
jti
relies on the rest of the claims, how do we ensure it is created last without hard-coding it?validateRefresh
requires confusing ternary (?) semantics. Some claims don't care (e.g.sub
), some have positive "OR" cases (e.g.exp
ORiat + refresh_ttl
is in the future), and some absolute rejections (e.g.nbf
is in the future). How do we handle those cases? In the above I'm usingnull
for don't care,true
for accept, and throwing and exception for reject, but that's a really weird triad. Also do we extend this behavior tovalidateCheck
?TimestampClaim
subclass that requires ablacklistUntil
function returning a timestamp of the last point a particular claim could trigger validity either for refresh or check. Then blacklist just takes the max of all these values and uses that for the cache timeout.Anyway I just wanted to put these thoughts out there while I had them. It might be a while til I can put together a working demo, and it might end up being too much complexity for the benefit. But I do think it simplifies some aspects and makes them a little more natural.