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

Integration of OIDC Flow via authorization_code grant #477

Open
denniskniep opened this issue Aug 8, 2024 · 3 comments
Open

Integration of OIDC Flow via authorization_code grant #477

denniskniep opened this issue Aug 8, 2024 · 3 comments

Comments

@denniskniep
Copy link

OIDC Flow

As-is Situation

Currently authorino is able to authenticate requests based on provided credentials by extracting them from the ext auth check request.

i.e extracting an jwt from the http header and verifying the signature. If successful than the authorino pipeline continues to the next stage.

Feature Request

It would be useful if authorino would be able to handle the flow for issuing IdTokens via OIDC Flow. Utilizing the OIDC standard

Motivation

Envoy has already an OAuth2 Filter which fulfills the basic requirements.
But currently it seems to be that there is not much appetite for making the OAuth2 Filter more feature rich. If someone wants to contribute a feature on their own, he need to catch a signer (envoy maintainer) and need to write that feature as C++ code.

The expectation is, that it will be easier to extend OIDC Flow inside authorino. Furthermore there will be then one single config which is repsonsible for AuthN & AuthZ and not spread accross several components configs.

Initial Scope of the Change

Handle oidc flow and issue IdToken with authorization_code grant. ClientId & Secret are sent via Basic Auth. The issued jwt can be set as httponly cookie to downstream, so that subseqeuent requests can skip the full flow.

Changes

API changes

Add OidcFlow config property to AuthenticationMethodSpec

type AuthenticationMethodSpec struct {
    ...
    OidcFlow *OidcFlowAuthenticationSpec `json:"oidcFlow,omitempty"`
}
type OidcFlowAuthenticationSpec struct {
    IssuerUri string `json:"issuerUri"`
    ClientId string `json:"clientId"`
    ClientSecret SecretKeyReference `json:"clientSecretRef"`
    RedirectUri string `json:"redirectUri"`
    CallbackPath string `json:"callbackPath"`
    Scopes []string `json:"scopes,omitempty"`
    TTL int `json:"ttl,omitempty"`
}

Add Cookie Response

Providing an easy to use config option for issuing cookies with Custom Responses

type WrappedSuccessResponseSpec struct {
	...
	ResponseCookies map[string]CookieSuccessResponseSpec `json:"responseCookies,omitempty"`
	ResponseHeaders map[string]HeaderSuccessResponseSpec `json:"responseHeaders,omitempty"`
}
type CookieSuccessResponseSpec struct {
    SuccessResponseSpec `json:",omitempty"`
    Domain string `json:"domain"`
    Path string `json:"path"`
    SameSite string `json:"sameSite"`
    Expires ValueOrSelector `json:"expires"`
    MaxAge int `json:"maxAge"`
    HttpOnly bool `json:"httpOnly"`
    Secure bool `json:"secure"`
}

It might be useful, if we could set the Expires property of the cookie based on the expiresAt value from the IdToken. Therefore using ValueOrSelector as data type.

Framework changes

Option to return AuthResult from IdentityPipeline

We need to influence the AuthResult from the pipeline.evaluateIdentityConfigs() to set specific status code and header to downstream client
(More precisely we need to respond with a redirect to the IDP, if the user does not provide any authentication information)

Proposal:

Create an wrapped error struct ErrorDenyWith
with the following properties:

Code    int32
Message *json.JSONValue
Headers []json.JSONProperty
Body    *json.JSONValue

If the returned error by pipeline.evaluateIdentityConfigs() is of type ErrorDenyWith
then it is overwriting the user configured pipeline.AuthConfig.Unauthenticated response! The ErrorDenyWith from the highest configuration priority wins.

Flow

  • Check if request contains idToken via GetCredentialsFromReq(). If yes, utilize logic from identity.OIDC
  • If unauthenticated Request (no jwt located in AuthCredentials), then return AuthResult with Authentication request which is redirecting to the IDP.
  • User is authenticating on IDP and afterwards redirected, so that request is processed finally by authorino
  • Receive the Authentication response including the code on the configured CallbackPath.
  • Issue Backchannel request from authorino to the TokenEndpoint of the IDP to exchange the code with token(s)
  • After verification OidcFlow writes the received IdToken and extracted Claims into the Authorization JSON
  • Configured in response phase, that a cookie should be issued with an appropiate name containing the IdToken via CustomReponseMethods

Improvements for Later

Improvements, that we can discuss and address later, because they are not essential for an intial MVP. Therefore they are explicitly out of scope:

  • Forward issued token to upstream
  • One URL for handling the redirect from IDP
  • Other ways for session handling (hmac, etc.)
  • state/nonce support
  • Configure how client_id and client_secret are sent to OAuth server requests
  • Authorization Code Flow with Proof Key for Code Exchange (PKCE)
  • Automatic refresh token
  • Specify tokenEndpoint, authorizationEndpoint etc. explicitly in config (request to /.well-known is not necessary)
  • Do not forward cookie with jwt to upstream (headers_to_remove)

Furthermore it might be a useful feature to introduce Sessions into authorino. So that the IdToken never leaves the authorino backend (i.e. inside a cookie).

@guicassolato
Copy link
Collaborator

Thank you so much for such a detailed description, @denniskniep!

I think we should put OIDC Flows in Authorino to a vote.

Hoping not to influence the process, I'll abstain in the vote and just leave here my personal take on the proposed changes, separated by topics:

OIDC Flows

Personally, I believe it should be part of Authorino's philosophy to be as transparent as possible to the API consumer, working rather on behalf of the server instead. Negotiating tokens on behalf of the consumer goes on the opposite direction, shifting Authorino's role, at least around this particular feature, towards becoming a proper client the consumer delegates power to (in OAuth2 terms).

Another concern of mine regards to the authorization_code grant in particular. It requires Authorino to expose an endpoint to receive the callback to then proceed with the code exchange request. Possibly, this may involve exposing the endpoint outside the Kubernetes cluster. This is a process that would run marginally to Authorino's main thread (and purpose) of validating incoming requests.

Automatic refresh token (listed as future improvement)

Authorino storing and managing consumers' refresh tokens raises yet another flag IMO. It means handling state beyond caching, thus adding new requirements.

Cookie Response

I think this is a valid addition worth extracting to a separate issue. Possibly, it would expand to modifying the response to the consumer beyond cookie setting (other headers and maybe body too?), but definitely introducing clearer distinction between this and modifying the request to the upstream (already supported).

I see this change as valuable to multiple other applications as well. One who's feeling adventurous enough could try combining it to anonymous access and OPA, and get full OIDC authorization_code grant flow in an AuthConfig before Authorino properly supporting it. It would also close #402.

API changes

We're now in the midst of rolling out a series of releases to push the AuthConfig API (as well as Authorino itself) to v1. At this point, additions to the API should target v1beta3 if they can make it by October 2024. The next window for API changes likely will be v2alpha1 sometime after November this year, tho arguably additions tend to be relatively easier to accommodate in general.

@denniskniep
Copy link
Author

Thanks for your feedback @guicassolato. Two comments:

it should be part of Authorino's philosophy to be as transparent as possible to the API consumer, working rather on behalf of the server instead.

I think it depends on the type of application that Authorino is serving. There are servers which actually do the OIDC authorization_code grant flow and not only consuming the token. In that particular scenario Authorino would act on behalf of the server.

It requires Authorino to expose an endpoint to receive the callback to then proceed with the code exchange request

I think a dedicated endpoint is not necessary. Because the callback happens within a redirect and this is always a GET Request. Therefore we can utilize the already established pipeline processing inside authorino. If the incoming request matches the configured CallbackPath, then execute the Backchannel request to the TokenEndpoint of the IDP to exchange the code with token(s).

@OperationalDev
Copy link
Contributor

This feature sounds like it would solve some challenges I've run into and it would be great if it could be incorporated.

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

No branches or pull requests

3 participants