Hydra - React SPA CSRF problem: "The CSRF value from the token does not match the CSRF value from the data store"

We’re in the process of building a new product which uses ORY Hydra and Oathkeeper for our authentication. The front end is a React SPA, which queries data from a GraphQL API on a separate server, and uses Hydra over a custom Identity Manager (another server) for authenticating users.

Locally, I have our stack set up behind a domain for local testing: xyz.localhost as a contrived example. We have each server sit behind a different subdomain:

  • app.xyz.localhost (where front end is served from)
  • api.xyz.localhost (GraphQL)
  • auth.xyz.localhost (Hydra)
  • identity.xyz.localhost (custom Identity Manager service)

Oathkeeper sits above api.xyz.localhost to introspect incoming tokens and authenticate/authorise access accordingly. Everything is initially routed on my local machine using NGINX (so we don’t have to stipulate port numbers).

The problem we’re having, is that with this set up we get to the point of consent correctly, but then the redirect issued to the client from IdentityManager as to where to go next results in Hydra returning the user to the client with the error_hint query as “You are not allowed to perform this action.”. Looking at the logs, the actual panic in Hydra has the debug value “The CSRF value from the token does not match the CSRF value from the data store”.

I’m failing to see how the CSRF cookie can be invalid as in debugging requests directly to Hydra it seems as if the cookie is present and doesn’t change between requests?

Any help would be much appreciated!

Make a request trace and see if there’s a 127.0.0.1 in there somewhere. Also check if the path of the cookie is correct, and check if there’s a secure flag. Be aware that oauth2/auth is not for AJAX, only oauth2/token

  • I’ve traced the specific request that’s causing the failure and the domains for each redirect in the request seem fine.
  • The cookie domain is set correctly. I can verify that both through the request trace headers and by heading to Hydra’s public endpoints in the browser from where the client issued the start of the OAuth flow - the cookie is displayed for the URL and is sent in the headers on the request to Hydra
  • We’re issuing redirects directly to the browser via 302s, not via XHRs in JS

I can’t for the life of me figure out what is causing the issue. I thought it might have been due to other posts I’ve seen on here about multiple flows kicking off at the same time but we’ve verified (and also put logic in the client to ensure) that it’s only hitting the oauth2/auth endpoint once to start the flow.

The issue is happening when Hydra handles the redirect to /oauth2/auth?client_id=<CLIENT_ID>&login_verifier=<LOGIN_VERIFIER>&redirect_uri=<REDIRECT_URI>&response_type=code&scope=<SCOPE>&state=<STATE>. The only requests Hydra has handled before that is the GET to /oauth2/auth to start the flow and the PUT to /oauth2/auth/requests/login/accept to accept the login - in this case, Hydra has told us to skip as the user has a current Hydra session.

Ok, but you’re not hitting the /oauth2/auth endpoint using ajax, right? You’re actually redirecting the browser there?

Another good idea is to trace the requests including headers. You can also check the cookie resources in chrome

Yeah the flow is initiated by doing a window.location assignment

Ok, if you’re able to export the trace of requests somehow I might be able to help

Hi, I am having the Same Issue Here.

After my login flow, Hydra redirects to the Initial URL.

hydra_1                     | time="2020-02-05T13:06:56Z" level=info msg="started handling request" method=GET remote="172.19.0.1:42918" request="/oauth2/auth?client_id=SomeServiceID&response_type=code&state=asdlkfex&redirect_uri=http://localhost:8080"
hydra_1                     | time="2020-02-05T13:06:56Z" level=info msg="completed handling request" measure#hydra/public: http://127.0.0.1:4444/.latency=1879300 method=GET remote="172.19.0.1:42918" request="/oauth2/auth?client_id=SomeServiceID&response_type=code&state=asdlkfex&redirect_uri=http://localhost:8080" status=302 text_status=Found took=1.8793ms
dashboard-login-provider_1  | {"Method":"LoginScreenHandler","level":"debug","msg":"Login Screen Handler","time":"2020-02-05T13:06:56.1439283Z"}
dashboard-login-provider_1  | {"Method":"LoginScreenHandler","level":"trace","msg":"Setting CSRF cookie","time":"2020-02-05T13:06:56.1441644Z"}
dashboard-login-provider_1  | {"Method":"LoginScreenHandler","level":"trace","msg":"Csrf template file \u003cinput type=\"hidden\" name=\"gorilla.csrf.Token\" value=\"/fuwZYB5Wt7Dy9p0BnBWBDsK0QG2hhSdXUofRtTiao8kVHY5ipDzAzAV3txVbaWIiFyNfTktgesR9Kq4kAyoNA==\"\u003e","time":"2020-02-05T13:06:56.1442568Z"}
dashboard-login-provider_1  | {"Method":"LoginEndpoint","level":"debug","msg":"Login Endpoint Handler","time":"2020-02-05T13:07:04.8201867Z"}
dashboard-login-provider_1  | {"Method":"LoginEndpoint","level":"trace","msg":"Form Submitted","time":"2020-02-05T13:07:04.8202552Z"}
dashboard-login-provider_1  | {"Method":"LoginEndpoint","level":"trace","msg":"Url http://id:8080/identity/login","time":"2020-02-05T13:07:04.8203808Z"}
dashboard-login-provider_1  | {"Method":"LoginEndpoint","level":"trace","msg":"Received response from the Identity service API {\"message\":\"Login Successful\"}","time":"2020-02-05T13:07:04.8384897Z"}
dashboard-login-provider_1  | {"Method":"LoginEndpoint","level":"trace","msg":"oauth2_authentication_csrf: oauth2_authentication_csrf=MTU4MDc1MjA5MnxEdi1CQkFFQ180SUFBUkFCRUFBQVB2LUNBQUVHYzNSeWFXNW5EQVlBQkdOemNtWUdjM1J5YVc1bkRDSUFJR0l3TURkaU9EVTNaVEl4TnpRNE1EVmlZakl5TTJFME5HWm1NV0UxT0RReXz33V9kPDKk4J30ug1tZ_n82Jv-V7wEqma_FGNzFh2yig==","time":"2020-02-05T13:07:04.8390199Z"}
dashboard-login-provider_1  | {"Method":"LoginEndpoint","level":"trace","msg":"Hydra URL http://hydra:4445/oauth2/auth/requests/login/accept?login_challenge=a4130e08224748d698ae1b07c89b2f47","time":"2020-02-05T13:07:04.8393891Z"}
hydra_1                     | time="2020-02-05T13:07:04Z" level=info msg="started handling request" method=PUT remote="172.19.0.5:39964" request="/oauth2/auth/requests/login/accept?login_challenge=a4130e08224748d698ae1b07c89b2f47"
hydra_1                     | time="2020-02-05T13:07:04Z" level=info msg="completed handling request" measure#hydra/admin: http://127.0.0.1:4444/.latency=718000 method=PUT remote="172.19.0.5:39964" request="/oauth2/auth/requests/login/accept?login_challenge=a4130e08224748d698ae1b07c89b2f47" status=200 text_status=OK took="718µs"
dashboard-login-provider_1  | {"Method":"LoginEndpoint","level":"trace","msg":"{\"redirect_to\":\"http://127.0.0.1:4444/oauth2/auth?client_id=SomeServiceID\\u0026login_verifier=c1bc94396ca64eee8d690122bc010d04\\u0026redirect_uri=http%3A%2F%2Flocalhost%3A8080\\u0026response_type=code\\u0026state=asdlkfex\"}","time":"2020-02-05T13:07:04.8421329Z"}
dashboard-login-provider_1  | {"Method":"LoginEndpoint","level":"trace","msg":"Login Response Body: map[redirect_to:http://127.0.0.1:4444/oauth2/auth?client_id=SomeServiceID\u0026login_verifier=c1bc94396ca64eee8d690122bc010d04\u0026redirect_uri=http%3A%2F%2Flocalhost%3A8080\u0026response_type=code\u0026state=asdlkfex]","time":"2020-02-05T13:07:04.8428352Z"}
dashboard-login-provider_1  | {"Method":"LoginEndpoint","level":"trace","msg":"Redirect sent to: http://127.0.0.1:4444/oauth2/auth?client_id=SomeServiceID\u0026login_verifier=c1bc94396ca64eee8d690122bc010d04\u0026redirect_uri=http%3A%2F%2Flocalhost%3A8080\u0026response_type=code\u0026state=asdlkfex","time":"2020-02-05T13:07:04.8433414Z"}
dashboard-login-provider_1  | {"Method":"LoginEndpoint","level":"debug","msg":"End Login Endpoint","time":"2020-02-05T13:07:04.8439566Z"}
hydra_1                     | time="2020-02-05T13:07:04Z" level=info msg="started handling request" method=GET remote="172.19.0.1:42938" request="/oauth2/auth?client_id=SomeServiceID&login_verifier=c1bc94396ca64eee8d690122bc010d04&redirect_uri=http%3A%2F%2Flocalhost%3A8080&response_type=code&state=asdlkfex"
hydra_1                     | time="2020-02-05T13:07:04Z" level=error msg="An error occurred" debug="No CSRF value available in the session cookie" description="The request is not allowed" error=request_forbidden hint="You are not allowed to perform this action."
hydra_1                     | time="2020-02-05T13:07:04Z" level=debug msg="Stack trace: \ngithub.com/ory/hydra/consent.validateCsrfSession\n\t/home/circleci/project/consent/helper.go:82\ngithub.com/ory/hydra/consent.(*DefaultStrategy).verifyAuthentication\n\t/home/circleci/project/consent/strategy_default.go:348\ngithub.com/ory/hydra/consent.(*DefaultStrategy).HandleOAuth2AuthorizationRequest\n\t/home/circleci/project/consent/strategy_default.go:966\ngithub.com/ory/hydra/oauth2.(*Handler).AuthHandler\n\t/home/circleci/project/oauth2/handler.go:623\ngithub.com/julienschmidt/httprouter.(*Router).ServeHTTP\n\t/go/pkg/mod/github.com/julienschmidt/[email protected]/router.go:334\ngithub.com/urfave/negroni.Wrap.func1\n\t/go/pkg/mod/github.com/urfave/[email protected]/negroni.go:46\ngithub.com/urfave/negroni.HandlerFunc.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/[email protected]/negroni.go:29\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/[email protected]/negroni.go:38\nnet/http.HandlerFunc.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:2007\ngithub.com/ory/hydra/x.RejectInsecureRequests.func1\n\t/home/circleci/project/x/tls_termination.go:55\ngithub.com/urfave/negroni.HandlerFunc.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/[email protected]/negroni.go:29\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/[email protected]/negroni.go:38\ngithub.com/ory/x/metricsx.(*Service).ServeHTTP\n\t/go/pkg/mod/github.com/ory/[email protected]/metricsx/middleware.go:261\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/[email protected]/negroni.go:38\ngithub.com/ory/hydra/metrics/prometheus.(*MetricsManager).ServeHTTP\n\t/home/circleci/project/metrics/prometheus/middleware.go:26\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/[email protected]/negroni.go:38\ngithub.com/ory/x/reqlog.(*Middleware).ServeHTTP\n\t/go/pkg/mod/github.com/ory/[email protected]/reqlog/middleware.go:140\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/[email protected]/negroni.go:38\ngithub.com/urfave/negroni.(*Negroni).ServeHTTP\n\t/go/pkg/mod/github.com/urfave/[email protected]/negroni.go:96\nnet/http.serverHandler.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:2802\nnet/http.(*conn).serve\n\t/usr/local/go/src/net/http/server.go:1890\nruntime.goexit\n\t/usr/local/go/src/runtime/asm_amd64.s:1357"
hydra_1                     | time="2020-02-05T13:07:04Z" level=info msg="completed handling request" measure#hydra/public: http://127.0.0.1:4444/.latency=4200100 method=GET remote="172.19.0.1:42938" request="/oauth2/auth?client_id=SomeServiceID&login_verifier=c1bc94396ca64eee8d690122bc010d04&redirect_uri=http%3A%2F%2Flocalhost%3A8080&response_type=code&state=asdlkfex" status=302 text_status=Found took=4.2001ms
hydra_1                     | time="2020-02-05T13:07:08Z" level=info msg="started handling request" method=GET remote="172.19.0.1:42938" request="/.latency=4200100"
hydra_1                     | time="2020-02-05T13:07:08Z" level=info msg="completed handling request" measure#hydra/public: http://127.0.0.1:4444/.latency=1936200 method=GET remote="172.19.0.1:42938" request="/.latency=4200100" status=404 text_status="Not Found" took=1.9362ms

Are you calling the endpoint using AJAX?