Comments (3)
Context and CSRF implementation
CSRF Token and Session State
Session in oauth2-proxy
is maintained in the SessionState
struct either:
- by encoding it and storing it in a cookie
- by storing it in a Redis instance
There is no explicit session ID in either case, however there is the Nonce
field for each session, which is a byte slice (randomly generated). This is apparently used in the login CSRF validation that oauth2-proxy
implements using the state parameter.
Using the Nonce
as the session ID would be a solution, however the Nonce
is sent to the identity provider and therefore can be leaked:
OIDCNonce holds the OIDC nonce parameter used in the initial authentication and then set in all subsequent OIDC ID Tokens as the nonce claim. This is used to mitigate replay attacks.
So is seems that the Nonce
can't be used as a session ID.
Therefore we introduced a new CSRFToken
field inside the SessionState
, which will be a random unique string, as per the token requirements in the specification.
Apart from the above, SessionState
seems a fitting place to store the CSRFToken
, since it already contains fields directly associated with the session and derived from the provider, i.e. id/access/refresh tokens.
For this purpose, we exposed the --csrftoken
argument, which is a boolean flag to enable/disable CSRF token generation on the session state upon login. This is disabled by default, to avoid breaking changes.
Returning the CSRF token to the client application
Once the CSRF token is generated and saved along with the session, it is necessary for the token to reach the client application. The ways to return a CSRF token to the client application are:
- with a cookie
- with a custom header
- with a JSON response on a designated endpoint
Note that the above can be used safely to return a CSRF token to the client, given that the proxy will expect client tokens in a custom header. Token value cannot be exploited e.g. by SameSite
when returning as a cookie, or by reading the request content - headers, response - when using the other methods, since cross-origin requests in Javascript don't expose the request and response objects.
Below I will describe the implementation for each.
Return as cookie
To return the CSRF token as cookie, we need to first construct the cookie. oauth2-proxy has the following Cookie struct:
// Cookie contains configuration options relating to Cookie configuration
type Cookie struct {
Name string `flag:"cookie-name" cfg:"cookie_name"`
Secret string `flag:"cookie-secret" cfg:"cookie_secret"`
Domains []string `flag:"cookie-domain" cfg:"cookie_domains"`
Path string `flag:"cookie-path" cfg:"cookie_path"`
Expire time.Duration `flag:"cookie-expire" cfg:"cookie_expire"`
Refresh time.Duration `flag:"cookie-refresh" cfg:"cookie_refresh"`
Secure bool `flag:"cookie-secure" cfg:"cookie_secure"`
HTTPOnly bool `flag:"cookie-httponly" cfg:"cookie_httponly"`
SameSite string `flag:"cookie-samesite" cfg:"cookie_samesite"`
CSRFPerRequest bool `flag:"cookie-csrf-per-request" cfg:"cookie_csrf_per_request"`
CSRFExpire time.Duration `flag:"cookie-csrf-expire" cfg:"cookie_csrf_expire"`
}
which is used to hold the necessary options to construct the session cookie using function MakeCookieFromOptions
(https://github.com/AthinaPl/oauth2-proxy/blob/master/pkg/cookies/cookies.go#L17).
We obviously can't use the same for the CSRF cookie, since it contains attributes CSRFPerRequest
and CSRFExpire
used for the CSRF cookie during login.
So we decided to create a new file and struct, in pkg/apis/options/csrftoken.go
to introduce our cookie, to also take advantage of the cfg
tags that populate the struct with the values taken from the configuration file or the CLI. This is done by using viper and pflag packages in file https://github.com/AthinaPl/oauth2-proxy/blob/master/pkg/apis/options/load.go.
This is the CSRF token struct
type CSRFToken struct {
CSRFToken bool `flag:"csrftoken" cfg:"csrftoken"`
CookieName string `flag:"csrftoken-cookie-name" cfg:"csrftoken_cookie_name"`
CookieDomains []string `flag:"csrftoken-cookie-domain" cfg:"csrftoken_cookie_domain"`
CookiePath string `flag:"csrftoken-cookie-path" cfg:"csrftoken_cookie_path"`
CookieExpire time.Duration `flag:"csrftoken-cookie-expire" cfg:"csrftoken_cookie_expire"`
CookieSecure bool `flag:"csrftoken-cookie-secure" cfg:"csrftoken_cookie_secure"`
CookieHTTPOnly bool `flag:"csrftoken-cookie-httponly" cfg:"csrftoken_cookie_httponly"`
CookieSameSite string `flag:"csrftoken-cookie-samesite" cfg:"csrftoken_cookie_samesite"`
}
We have also implemented a CSRFTokenDefaults()
function that sets default values to a CSRFToken
, and a MakeCSRFTokenFromOptions
function to create a CSRF token cookie from default values.
The above cookie attributes are controlled by the new flags:
--csrftoken-cookie-name
(string): The name of the cookie holding the CSRF token for the session. If set to empty string, the proxy will disable returning CSRF token as cookie. Default is_oauth2_proxy_csrftoken
.--csrftoken-cookie-domain
(string | list): The domain(s) (optional) of the CSRF token cookie. Default:""
--csrftoken-cookie-path
(string): The path of the CSRF token cookie. Default:/
--csrftoken-cookie-expire
(duration): The duration of the CSRF token cookie. Default is equal to the session cookie expiration, i.e. 168 hours.--csrftoken-cookie-secure
(bool): Set secure (HTTPS) cookie flag for CSRF token cookie. Default:true
.--csrftoken-cookie-httponly
(bool): SetHttpOnly
cookie flag for CSRF token cookie. Default:false
--csrftoken-cookie-samesite
(string): SetSameSite
cookie attribute for CSRF token cookie (ie:lax
,strict
,none
, or""
). Default is "strict".
The cookie, if--csrftoken=true
and--csrftoken-cookie-name!=""
, will be set during the handling of the callback function in the OAuth2 cycle.
Return as header
To return the CSRF token as a custom header, we introduced the:
--csrftoken-response-header
(string): The name of the actual CSRF token header to return to client. Default:X-CSRF-Token
.
that injects the header with the CSRF token value from SessionState
in each response of the oauth2-proxy
. If the flag is set to an empty string, the proxy will disable returning the CSRF token as header.
Return as JSON response
To return the CSRF token as a JSON response, we exposed the /oauth2/csrftoken
endpoint in the proxy. This is an authenticated endpoint (i.e. needs a session cookie) serving GET requests, and returns a simple JSON containing the CSRF token.
The purpose of this endpoint is to use right after the finish of the OAuth2 cycle, before making any requests to the upstream server. A frontend application can place an Ajax request to fetch the CSRF token and then include it in subsequent requests to the server.
CSRF validation
The validation of CSRF tokens is necessary if all of the following apply:
- the proxy operates on CSRF mode (
--csrftoken=true
) - a request is using an unsafe method according to RFC 7231
- a request is authenticated via a session cookie
Focusing on the last item, CSRF vulnerabilities happen in client applications (UI) and don't concern programmatic client requests, that authenticate with bearer tokens via authorization headers. This information is necessary when deciding to apply CSRF validation and is currently not implemented in oauth2-proxy
.
Authentication in oauth2-proxy
happens with chained middlewares, and the request context is passed to the proxy, which will check for a valid session before doing any more actions. Validation of the CSRF token takes place right after ensuring a session in the proxy. So it seems optimal for the request context to contain the authentication method information, to allow the proxy to decide if CSRF validation is necessary.
The above summarize the necessity of the following newly introduced argument:
--csrftoken-header
(string): The name of the header holding the CSRF token in incoming client requests. Default:X-CSRF-Token
Furthermore, RequestScope
now has a AuthMethod
field, that is set to:
cookie
, if request was authenticated via cookieheader
, if request was authenticated via the authorization header
Skip validation
In many setups, is it necessary to skip validation for some routes. To allow this functionality, we exposed the:
--skip-csrftoken-route
(string | list): Format:method=path_regex
ORmethod!=path_regex
. For all methods:path_regex
OR!=path_regex
. Default:""
This will construct a list of method/path combinations that requests should be allowed without CSRF validation. Note that we can completely disable CSRF validation, but not CSRF token generation, by including all paths in this argument.
from oauth2-proxy.
Pending items
The above summarize the operation of the CSRF implementation on the proxy. However, since there are cases that oauth2-proxy
is configured with upstreams, it seems necessary to allow:
- forwarding/removing the client CSRF token header: this was the intention of the proposed
--pass-csrftoken-header
, which after implementing the main functionality might be unnecessary, since we can simply use--skip-auth-strip-headers
flag to remove the client CSRF token along with the other authentication headers (X-Forwarded-*
andAuthorization
) - forwarding the authentication method: implement a new header that will be proxied upstream to relay the authentication method.
to the upstream. This information is necessary if the upstream server implements CSRF and doesn't need oauth2-proxy
to do so.
We have already started working on the above and will add the commits to the PR as soon as they're ready.
from oauth2-proxy.
Authentication method header
If CSRF validation is implemented on the upstream, there is no need to enable the feature in the proxy. However, not all requests are subject to CSRF checks - as we mentioned in previous comments:
The validation of CSRF tokens is necessary if all of the following apply:
- the proxy operates on CSRF mode (--csrftoken=true)
- a request is using an unsafe method according to RFC 7231
- a request is authenticated via a session cookie
Since we have added AuthMethod
to the proxy's RequestScope
, we need to allow passing this information to the upstream.
Currently the headers middleware that handles adding headers to requests forwarded to upstreams can get header values either from the session (ClaimSource
at https://github.com/oauth2-proxy/oauth2-proxy/blob/master/pkg/apis/options/header.go#L31) or from env/file (SecretSource
at https://github.com/oauth2-proxy/oauth2-proxy/blob/master/pkg/apis/options/common.go#L11). To allow fetching the authentication method from the RequestScope
, we implemented a ScopeSource
that fetches fields from the RequestScope
.
Furthermore, it is necessary for the header injector interface to have access to the scope. Currently the injector uses the SessionState
(which is fetched from the request scope). It seems logical for the injector to manipulate the RequestScope
instead of the SessionState
, and use RequestScope.Session
specifically when it needs it.
Remove client CSRF token header from request if --skip-auth-strip-headers=true
Client headers are normally included in the requests forwarded to the upstream. It is necessary for allowing CSRF validation on the upstream to pass the client CSRF token, but it also useful to remove it (if CSRF is disabled or if validation happens on the proxy or if we don't trust the upstream).
For the above reasons we opted to remove the header from the request when proxy is configured with --skip-auth-strip-headers=true
.
from oauth2-proxy.
Related Issues (20)
- [Feature]: options for add files in /oauth2/static/ HOT 4
- [Feature]: Guides for rauthy and/or authelia
- [Bug]: Unable to use hyphen in JSON path for oidc-groups-claim option
- [Bug]: Invalid authentication via OAuth2 via Github for the owner of the organisation HOT 8
- [Bug]: Possible typo in source code for static upstreams HOT 2
- [Bug]: Incomplete source of request urls for skip_auth_routes feature
- [Bug]: Redirect after second google login to home page not working
- [Support]: 401 Authorization Required even finished authentication HOT 1
- [Feature]: use username (or any other attribute from the provider) in basic auth header instead of the ID
- [Feature]: JWT validation only mode HOT 8
- [Bug]: An invalid redirect to a non-whitelisted domain creates a valid session cookie after redirecting to "/"
- Pass bearer token to the backend with nginx
- [Support]: Multi-Domain Forward-Auth with Traefik/k3s
- [Feature]: [Azure] Support certificate-based flow for requesting access token HOT 1
- [Feature]: Support for dry-run
- [Support]: failed to verify id token signature
- [Bug]: Setting `proxy-prefix` in helm seems to break login
- [Bug]: Azure provider: problem with ProfileURL/ userInfoURL (duplicate of closed issue #2162 )
- [Support]: <Keycloak-OIDC failed> HOT 1
- [Bug]: GitHub private repo check throwing 500 instead of 403 when user does not have access
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from oauth2-proxy.