-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
oauth2: allow issuing of JWT access tokens #248
Comments
This will no longer be pursued, instead, we will introduce Oathkeeper which is capable of translating opaque access tokens to JWT tokens. |
Wow. There should be a really big red warning: hydra no longer supports JWT as access keys. Too bad I'm only discovering it now. The solution I'm working on really requires JWT as access tokens. Is it possible to we add support for JWT still? |
When was that? Back in 2015?
Not in Hydra because of multiple issues (I'll write a blog post on it soon). But you can use https://github.com/ory/oathkeeper for that. It converts access tokens to JWTs and can also validate the request based on scopes and access control policies. Just be aware that we're currently refactoring it. |
Yes, somewhere around 2015. I didn't use it then, but I stumbled upon and noted it. So, how do I use Oathkeeper? Is it a replacement for Hydra? |
|
Thanks for the links, that's something that can be addressed later. Though, currently things kind of rely on implementing things within the constraints.
I need an access token extended with custom claims.
What drawbacks are you referring to? The main point of accepting JWT there is to be able to verify the token without the access to auth server, or knowing which one (of many) auth servers issued the token.
Seems like it has to sit in front of the API. It's not obvious if it would work if we have not just Hydra, but also other issuers that provide JWTs. It doesn't seem like it. |
You can't revoke tokens and the payload is transparent.
With the refactor, it could! It would even streamline the payloads |
Oh, yeah, but that's no big deal. Benefits are more important.
It'd be cool to have something like that, however it looks like it wont work now. I really missed that lack of JWT support, and I basically already spent all the time I could on integrating with Hydra. |
Blacklisting requires a network roundtrip which in turn negates the request-less validation.
Sorry, JWTs won't be added to the project in the foreseeable future. |
That's ok, though I'd honestly recommend adding a note about the lack of JWT somewhere. It was really unpleasant to find out about it in the issues when the integration is done. That'd be at least some improvement in this area. |
Yeah, we should probably have a "limitations" section in the documentation, I guess that would be really useful. Tracked as #839 |
Blacklist only has (and, in our case, can, since there's no single issuer that's responsible for all tokens) to live at the validation side, so no roundtrip is required there. |
Unless you store the black list in memory, you will always have a roundtrip. Be it to the filesystem, or to the database. Sure, some dbs will perform better/faster for this specific task than a HTTP rountrip, but it adds the same possibility of failure. I understand that you have multiple issuers which you want to trust where resource-provider-side validation makes more sense than requesting info from every issuer. It is however not according to the revokation spec, which specifically states that the token must be invalidated at the authorization server, not the resource provider. The problem is that the token will be marked as active at the authorization server, despite it not being active at the resource provider. Also keep in mind that revoking tokens is not just invalidating one token, but also "reovke tokens for all applications" or "revoke tokens for application x" Indeed, it's quite the intriguing use case. Usually, apps accept tokens from one or two issuers, not many - so this is actually the first use case I've encountered where JWTs truly make sense. I still think it could be solved via oathkeeper, but I also understand the increase in complexity. You could obviously write something like oathkeeper yourself which is able to resolve tokens that aren't JWTs - you'd have all the benefits of revoking tokens. Revoking tokens is a really important security mechanism that can prevent major threats if applied correctly. Not having that work properly may cause significant issues down the road. |
I need something really simple for the job. I'd use opaque tokens, but need to distinguish between the issuers to avoid verifying with all the issuers. Also typically different issuers will be only available in different environments. This authentication model is fundamentally different than the usual one, it's kind of more decentralized. Relying on the ability to revoke a token is not always good. In some cases it's just not correct. It's not a silver bullet, and there are many ways to cover attack vectors that are typically covered by token revocation. Seems like OAuth 1/2 had a paradigm shift, early on it was more like a framework that allowed, and, to some degree, encouraged implementing custom enhancements. Now it seems to be more like a set of some strict normatives. I'm counting that (the culture change, I guess) as the reason why support for JWT was eventually dropped. |
Of course it's not a silver bullet, but it's a critical tool in your toolbox. |
fyi as a drive-by comment, we were keen to find an OAuth impl to integrate with an existing product (that already has it's own OAuth server) which expects access tokens to be in the JWT format, and not supporting them will rule this out. (as a side note, I fully agree that JWT is an inappropriate format for refresh tokens, so please read the below in the context of access tokens only) I disagree about the drawbacks listed, and thought it might be productive to list the reasons why, in case it informs future product direction: "You can't revoke tokens"Correct. And that trade-off is why most access tokens are short-lived (ie 1 hour or less). I think an informed implementor can make this trade-off, which is a short period of irrevocability vs the benefits of statically verifiable tokens (which is good from both a latency and resiliency view). "The payload is transparent"For many resources server that is totally OK. The payload is essentially:
These are not secret - and there is a latency and reliability benefit to being able to statically verify these. |
Btw, I think, if we really need revocation of JWT token, nothing stops us from supporting that. If we place an opaque string inside of a JWT, in a custom key, it can be effectively used to verify the session. |
Actually, I agree. I think we could let developers make the trade off between timeliness of access revokation and avoiding network roundtrips.
If you know what you're doing, then yes. However, developers are incredibly lazy (like mathematicians) and what happens often is that over time additional data is added to the token because "it's already there and I guess the data isn't really private", but it actually is. I'm not saying that this is the norm, just that this happens and is observable in the wild. The question is if this project should protect you from doing this by enforcing an (opinionated) way of issuing access tokens or not. So far, my belief was that yes - we should enforce secure behavior even if it breaks the 1% use cases. I am, however, today more convinced that certain practices could be allowed if the devs know what they are doing. Having the default of issuing opaque tokens while also having a
That's negating the benefits of validating the tokens without an additional network roundtrip :) |
To keep this discussion alive, I'm reopening the issue. |
Using jwts can be useful for service to service communication with password credentials grant. Let's assume the common use case I have been seeing where opaque access tokens are terminated in an api gateway and transformed into JWTs internally to be consumed by various services (something like Oathkeeper is capable of) Under this assumption, let's say service A wants to communicate with service B. It goes to Hydra and does the password flow to get an opaque access token. Then it calls service B with that access token. One option would be to route service A --> B request through a gateway like Oathkeeper so that they will be terminated as well, but that's an overhead for internal service communication. Also, at this point, if all services are sharing a secret, why having the overhead of calling hydra to get access token and then calling hydra again to validate that access token on the other side. They could just use a pre shared jwt as a secret and use that for authentication between them |
@arekkas, I’m catching up on this issue, but there’s an inportant point I’d like to state, regarding adding fields to jwt: it’s pointless to try to guard developers from stupidity (in the code level). There’s always a way to do things wrong. That’s why we have seniors, architects and other people that protect the dynamic systems from moving into wrong directions. |
Yeah, but what if you only need to validate the session in some cases (i.e. half of the endpoints)? |
Just a quick plug here - I'd love for there to be JWT support for access tokens, I'd like it even better if we could plugin custom JWT signing mechanisms. I mention this because I've effectively (read: foolishly) hacked the hydra bootstrap process to get my own database and JWT signing mechanism in place. I also had to wrap the I know for the database part I could try the plugin route, but this doesn't make sense for my backend. There currently exist nothing for plugging in a custom signing mechanism. If this opens the possibility to selecting a signing mechanism, maybe this can be made to be customizable? I'd suggest to use the model used by the https://github.com/dgrijalva/jwt-go package where you can dynamically register token signing types (which I did for GCP). Just food for thought, I'd also like to see that for the database side too and would be happy to suggest a simple modification to the current codebase to enable this or even submit a PR! I hope this all made sense, I'd be happy to discuss in chat or elaborate here if anything is unclear. EDIT: Also, the use of oathkeeper doesn't make sense for my use case as I already plan on using an API Gateway from my cloud provider (not an equal comparison but still relevant) - I have an existing IDP to layer hydra on top of and can leverage other existing technologies for protecting my oauth2 protected endpoints- in my case an API. |
Is there a particular reason for using a somewhat more exotic signing algorithm (like |
By the way, tampering with dynamically accepting token signing types can be a serious security vulnerability. The typical attack path is to change the |
Well I'd actually utilize RS256 myself, but leave the signing to GCP - they manage secrets and rotate keys so I don't need to worry about it.
RE accepting token signing types: I'm aware of this and the jwt package does a good job of explaining how to mitigate this. I don't think this is a reason to prevent enabling the usage of such mechanisms - just helps those of us looking to extend the capabilities while adding arguably little-to-no overhead to the core hydra features/goals. EDIT: Removed mention of KMS - not relevant for handling signing, only encryption. However, I know GCP offers a signing API with backend key management, not sure about AWS. |
Does the Google KMS sign JWTs or just provide the keys via an API? I generally like the idea of integrating with KMS but would like to see a generic solution that spans GCP, AWS, Azure, (DigitalOcean?) as opposed to having a sizeable code base and maintenance overhead just to integrate with each proprietary system (and its breaking changes wrt to authorization and API usage, which has bitten me often - specifically the auth* part). |
ps: key rotation is now a first-class citizen in hydra as that was a requirement for getting the OpenID Dynamic profile certified - just tell hydra to generate a new key |
I don't think you'd get a generic solution that spans multiple providers. Just like you do with database backends, I'd expect a generic interface each implementation would have to satisfy for the backend wanted. I don't expect this to be a part of hydra, but something that we (as developers) could inject in a custom hydra deployment without having to do all the copy pasta as I did in my repository. I was mistaken in thinking GCP/AWS KMS does signatures, it only does encryption/decryption. I do utilize GCP's IAM API to sign blobs and utilize them for JWTs - it uses the RS256 algorithm. I believe Vault by HashiCorp also allows secrets management with signing capabilties. There could be other such systems out there that may be more secure than using what's baked into hydra, or just to enable separation of duties. I don't believe hydra would incur any maintenance overhead in enabling dynamic registration of backends for this, it would just ship with reasonable defaults. To illustrate a bit on how I envision the change to be, I'll take the backend example where you can use a plugin vs SQL: Instead of a switch for the types supported, an interface would be defined for the backend registered that returns the manager type required. handler_oauth2_factory.go becomes: // Showing a simple interface for this specific file, probably would be a more generic interface that had functions which returned the proper backends for each of jwk/oauth2/client/consent/etc.
type FositeStoreManager interface {
NewFositeStore(clients client.Manager, lifespan time.Duration) pkg.FositeStorer
}
var fositeBackends = make(map[string]FositeStoreManager)
func init() {
// Load default supported backends
fositeBackends["memory"] = oauth2.FositeMemoryStore
fositeBackends["mysql"] = oauth2.FositeSQLStore
}
func RegisterFositeBackend(prefix string, backend FositeStoreManager) {
fositeBackends[prefix] = backend
}
func injectFositeStore(c *config.Config, clients client.Manager) {
var ctx = c.Context()
var store pkg.FositeStorer
if backend, ok := fositeBackends[ctx.DatabasePrefix]; ok {
store = backend.NewFositeStore(clients, c.GetAccessTokenLifespan())
} else {
panic("Unknown connection type.")
}
ctx.FositeStore = store
} Then in a package such as mine I could register such as: import "github.com/ory/hydra/cmd/server"
func init() {
server.RegisterBackend("datastore", MyDatastoreImplementation{})
} |
That's a good idea - it's already possible to do that with plugins, so no need to compile it your on your own (at least not hydra). But it doesn't allow you to replace just one store, you have to write implementations for all of them (but you can obviously import e.g. I'm not sure how many people compile hydra on their own and if adding this feature would benefit the 95% use cases. I do agree with you on separation of concerns and generally like the idea of integrating with cloud provider's KMS systems. The default store (SQL/Memory) will definitely not be removed as hydra should work standalone, but I don't think you're suggesting that anyways. Maybe it would be enough to just provide |
I'd love to see Vault support as well, and/or custom HSMs (for baremetal deployments). |
In general, an API to use external signer is better than a bunch of the built-in ones. Take a look at caddy server model of plugins - it's very easy to use and also very flexible. I think implementing a good extensibilitry model brings benefits beyond the scope of just JWT support, and would help project to move in the right direction in the long term. |
The current plugin system for database backends isn't generic enough, it expects a Either way, I'm really glad to hear that we can possibly support a pluggable system for signing here - I'd be happy to discuss this more design-wise and help with implementation if you'd like! |
I don't think it does. There is a *sqlx.DB reference but that is for re-using the already existing database connectivity if you want to. I admit that the code is a bit weird there but it's definitely possible to ignore *sqlx.DB and use your own connectivity.
It's not a super-high priority for us right now (first things first like getting out of beta for all services, enhancing the console, working on hive) but it's definitely something worth exploring once there's a bit of downtime. |
Sounds good, I'll keep an eye on this issue when development starts. The *sqlx.DB reference for plugins is required as when its loaded an immediate call to |
Yeah you're right, feel free to track this whole discussion as a new issue as this one will probably be closed once JWT lands. |
This patch adds the (experimental) ability to issue JSON Web Tokens instead of ORY Hydra's opaque access tokens. Please be aware that this feature has had little real-world and unit testing and may not be suitable for production. Simple integration tests using the JWT strategy have been added to ensure functionality. To use the new JWT strategy, set environment variable `OAUTH2_ACCESS_TOKEN_STRATEGY` to `jwt`. For example: `export OAUTH2_ACCESS_TOKEN_STRATEGY=jwt`. Please be aware that we (ORY) do not recommend using the JWT strategy for various reasons. If you can, use the default and recommended "opaque" strategy instead. Closes #248 Signed-off-by: arekkas <[email protected]>
This patch adds the (experimental) ability to issue JSON Web Tokens instead of ORY Hydra's opaque access tokens. Please be aware that this feature has had little real-world and unit testing and may not be suitable for production. Simple integration tests using the JWT strategy have been added to ensure functionality. To use the new JWT strategy, set environment variable `OAUTH2_ACCESS_TOKEN_STRATEGY` to `jwt`. For example: `export OAUTH2_ACCESS_TOKEN_STRATEGY=jwt`. Please be aware that we (ORY) do not recommend using the JWT strategy for various reasons. If you can, use the default and recommended "opaque" strategy instead. Closes #248 Signed-off-by: arekkas <[email protected]>
This patch adds the (experimental) ability to issue JSON Web Tokens instead of ORY Hydra's opaque access tokens. Please be aware that this feature has had little real-world and unit testing and may not be suitable for production. Simple integration tests using the JWT strategy have been added to ensure functionality. To use the new JWT strategy, set environment variable `OAUTH2_ACCESS_TOKEN_STRATEGY` to `jwt`. For example: `export OAUTH2_ACCESS_TOKEN_STRATEGY=jwt`. Please be aware that we (ORY) do not recommend using the JWT strategy for various reasons. If you can, use the default and recommended "opaque" strategy instead. Closes #248 Signed-off-by: arekkas <[email protected]>
This patch adds the (experimental) ability to issue JSON Web Tokens instead of ORY Hydra's opaque access tokens. Please be aware that this feature has had little real-world and unit testing and may not be suitable for production. Simple integration tests using the JWT strategy have been added to ensure functionality. To use the new JWT strategy, set environment variable `OAUTH2_ACCESS_TOKEN_STRATEGY` to `jwt`. For example: `export OAUTH2_ACCESS_TOKEN_STRATEGY=jwt`. Please be aware that we (ORY) do not recommend using the JWT strategy for various reasons. If you can, use the default and recommended "opaque" strategy instead. Closes #248 Signed-off-by: arekkas <[email protected]>
Basically, this means one can't use Kafka OAUTHBEARER OIDC with Ory Hydra because of not supporting JWT, which seems to be very frustrating... Azure AD, Keycloak both provide valid JWT tokens.
Did I miss anything here? Edited: |
No description provided.
The text was updated successfully, but these errors were encountered: