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

[WIP] OAuth2 Middleware: Store token in a cookie #2963

Closed
wants to merge 7 commits into from

Conversation

ItalyPaleAle
Copy link
Contributor

@ItalyPaleAle ItalyPaleAle commented Jul 3, 2023

Replaced by #2964

Fixes #2635

Currently the OAuth 2 middleware suffers from scalability issues as reported in #2635. Tokens obtained from the authorization server are stored in-memory in the Dapr runtime (a session ID is stored in a cookie on the client), so they do not persist if the runtime is restarted, and make it impossible to scale the application horizontally.

The most common solution to this problem is to store the tokens in the clients, as self-contained tokens such as JWTs; because tokens are sensitive, they are encrypted (using encrypted JWTs).

Because cookies now store the token encrypted, users need to provide an encryption key via the new metadata property cookieKey:

  • This property is required.
  • For backwards-compatibility, in Dapr 1.12 this property is optional and, if missing, a key will be randomly-generated by the runtime. When this happens, the key is ephemeral so tokens do not survive a restart of the Dapr runtime and horizontal scaling is not supported - this is pretty much what happens in Dapr 1.11 today! I have opted for allowing this behavior in Dapr 1.12 to offer a smoother transition period (even if the component is in alpha stage).
  • Technically speaking, cookieKey is not the actual encryption key used: the Dapr runtime deterministically derives both a signing key and an encryption key from that

Work In Progress

This PR is currently a WIP because of an issue with handling large tokens.

  • Some IdPs can return tokens that are very large. Chief among them is Azure AD, which recently increased the size of the auth tokens it issues. I have now observed that a token from Azure AD can be well over 2KB without many claims; some users report seeing tokens much larger than that.
  • Although the specs don't put any limit on the size of cookies, most browsers seem to silently reject cookies that are larger than 4KB (technically, 4093 bytes)

Solutions:

  • The most immediate fix was to enable compression on the JWT. Encrypted JWTs are based on JWE, and RFC7516 that defines JWE does include support for compression. Compression is enabled by default if the token is larger than 800 bytes. However, compression helps only to a certain point, and Azure AD can return tokens that are much larger.
  • Two possible solutions have not been implemented yet:
    1. Currently the component stores tokens only in cookies. We could offer an option to use headers, such as the Authorization header (which has less problems with data size), instead. This would not work well if clients are browsers, but other apps should be fine.
    2. We could offer optional support for storing the token on the server-side, likely using an internal actor (see Allow (middleware) components to define internal actors dapr#6538). Actors seem well-suited for this use case because they allow using all supported state stores, and the use of reminders allows us to efficiently clean up expired sessions. In this case, if the token was too large to be self-contained in a cookie, we'd store it in an actor and only send a reference (a "session ID") to the user.

Includes validation

Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com>
Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com>
Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com>
Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com>
Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com>
Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com>
Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com>
@ItalyPaleAle
Copy link
Contributor Author

Closing in favor of #2964

Copy link
Contributor

@drewby drewby left a comment

Choose a reason for hiding this comment

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

Overall, looks good given the cookie limitations. I put some minor comments and a question inline.

Given the limitations, I think you still need a solution that uses a distributed cache or some shared session store server side.

}
const (
// Timeout for authenticating with the IdP
authenticationTimeout = 10 * time.Minute
Copy link
Contributor

Choose a reason for hiding this comment

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

This is named and documented as a timeout for the authentication request, but it is used as a TTL on the auth token / cookie.

Both may be necessary (the authenticationTimeout as a circuit breaker on the IdP).

Also, should these be configurable?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes it's a timeout on authentication requests. Because we cannot influence the timeout on the IdP, we can only set an expiration on the cookie with the state token.

I am not sure this needs to be configurable. 10 mins should be plenty for someone to authenticate.

middleware/http/oauth2/oauth2_middleware.go Show resolved Hide resolved

// Browsers have a maximum size of about 4KB, and if the cookie is larger than that (by looking at the entire value of the header), it is silently rejected
// Some info: https://stackoverflow.com/a/4604212/192024
if len(cookieStr) > 4<<10 {
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens if the backend service also adds cookie values on subsequent requests? Is the total limit still 4k?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it's still 4KB per domain in most browsers. If the cookies go over the limit, they are silently dropped

@ItalyPaleAle
Copy link
Contributor Author

Thanks for the review! @drewby This helps. I do want to implement support for storing tokens on the server, and that will likely involve using Dapr Actors. I am tracking that as a follow-up when #2967 is merged

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.

Oauth2 Middleware scalability limit and resilience issues with code grant flow and token storage
2 participants