SubmitSelfServiceLoginFlowWithHttpInfoAsync for Aal2 fails using .NET client in SPA (browser mode)
Preflight checklist
- [x] I could not find a solution in the existing issues, docs, nor discussions.
- [X] I agree to follow this project's Code of Conduct.
- [X] I have read and am following this repository's Contribution Guidelines.
- [ ] This issue affects my Ory Cloud project.
- [ ] I have joined the Ory Community Slack.
- [ ] I am signed up to the Ory Security Patch Newsletter.
Describe the bug
Trying to submit a login flow for Aal2 using .NET client in SPA (browser mode). This requires passing both csrf and ory_session cookies. No clear way in the SDK docs to do this. The only options seems to use Configuration.DefaultHeaders["cookie"] (tested and working for Aal1 that requires only csrf cookie). Setting both cookies (concat cookies using '; ') using this strategy does not work. Also tried to rewrite the method SubmitSelfServiceLoginFlowWithHttpInfoAsync (from source) to add both cookies using the lines below:
if (sessionCookie != null)
localVarRequestOptions.HeaderParameters.Add("cookie", sessionCookie.ToString());
if (csrfCookie != null)
localVarRequestOptions.HeaderParameters.Add("cookie", csrfCookie.ToString());
but again it does not work if settings both cookies (only works for one).
Please advise.
Reproducing the bug
Create a .NET SPA web application (e.g. Blazor server app). Add the Ory.Client Nuget. In the code, use the SDK to initialize a login flow for Aal2, fill the form data and call SubmitSelfServiceLoginFlowWithHttpInfoAsync.
Relevant log output
No response
Relevant configuration
No response
Version
Ory.Client 0.0.1-alpha.30
On which operating system are you observing this issue?
Windows
In which environment are you deploying?
Binary
Additional Context
No response
Thank you for the report! It’s very unfortunate that this SDK method is difficult to use. We will work towards improving the SDK use. At the moment most clients are autogenerated.
One option you have is to let the browser HTML form do the submission. Then you do not need to worry about the right tokens :) Could you maybe explain a bit where you use the SDK method? That could help me better understand what guidance to give you!
I looked at our swagger definitions and it looks like the submit login method is missing the cookie parameter. So it’s something we can address fairly quickly in Kratos, and merge then downstream in Cloud :)
Since it’s EoD here, you’ll probably have the fix by tomorrow, latest Tuesday!
Thank you for your reply.
Yes, the (session) cookie parameter is missing in the submit login method, but looking at the SDK source (for instance ToSessionWithHttpInfoAsync) you can see that the cookie is passed to the underlying request as:
localVarRequestOptions.HeaderParameters.Add("Cookie", Ory.Client.Client.ClientUtils.ParameterToString(cookie));
And a working workaround is to set the:
ClientApi.Configuration.DefaultHeaders.Add("Cookie", cookie);
which is later added to the localVarRequestOptions.HeaderParameters as intended.
Currently the problem is when two cookies are needed for the SubmitSelfServiceLoginFlowWithHttpInfoAsync method to work (in this case the csfr and the session cookies). Either changing the SDK source with:
localVarRequestOptions.HeaderParameters.Add("Cookie", csrfCookie);
localVarRequestOptions.HeaderParameters.Add("Cookie", sessionCookie);
Or merging both cookies in the DefaultHeaders:
ClientApi.Configuration.DefaultHeaders.Add("Cookie", csrfCookie + "; " + sessionCookie);
Always throws a no active session exception.
Futher, if I only send the session cookie, a CSRF exception is thrown and if I only send the csrf cookie, a no active session exception is thrown, so it works when sending only one cookie...
Let me know if there is something else I can try.
Got it, thank you for the great explanation! One question I have is whether you need to submit the form using programmatic access or if it would be possible to submit the HTML form as is:
<form action="https://..." method="post">
...
</form>
I am asking because the browser should send all the cookies automatically, which is why the SDK submission methods do not have a cookie parameter at the moment.
Or are you proxying the requests to the API?
We're developing an SPA application, so using the programmatic accessor seems to be the right way to interact with Ory. After some sniffing around, we figured out what the problem is. The Ory server application is only splitting multiple cookies sent in the Cookie HTTP header if they are terminated with ";", this is why it was not working. I don't think this is standard behavior (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie). We already updated our client side code to cope with this, so it now seems to be working. If we find any other difficulty that we can figure out, we will let you know.
Got it, thanks! I'm not sure why it needs to be terminated but it appears to be the requirement of the Go Cookie package so I'm not sure if we can change it.
One question I have is, if the application is running in the browser (so SPA), shouldn't thw browser send the cookies automatically alongside the AJAX request even if you do programmatic access? Or does this setup work differently when using .NET?
The client browser send the async request to our custom UI page (our server) which in turn uses the Ory SDK to pass the information to the Ory Servers. From our understanding of the Ory SDK for .NET, the ClientApi relies on Restsharp to send requests to the Ory server endpoints. Each request is filled with cookies, headers, form data, etc. manually using the ClientApi.Configuration class and the provided method arguments. No information from the current HTTP Context is used (or present at that stage) so no cookies, headers, etc. are set automatically from the client (browser). This is why we are manually passing the required cookies via ClientApi.Configuration.DefaultHeaders.
Regarding using the session cookie returned after a successful call to submit login, this is also not automatically added to the client browser. We also did not find information on this in the docs, but it is our understanding that we will need to redirect the client to another page in order to pass the session cookie via HTTP Cookie Header (once this cookie should not be set using JS for security reasons - cross-site scripting XSS). If there is a better way of doing this, let me know.
I see! We are still working on improving the documentation around how these flows work (in particular, writing your own UI). The general idea is to not intercept any requests but instead let the browser deal with this stuff. That way, you don't have to work with any of the cookies and others. That is why the cookies are not part of the SDK.
You can find a JavaScript example of that here (SPA with ReactJS):
https://github.com/ory/kratos-selfservice-ui-react-nextjs/blob/37dbc630d4a495e2de64fcb807316150621a7938/pages/login.tsx#L80
There, we are making the AJAX request (and calling the SDK) directly in the browser, and to the Ory API - you can use the Ory Proxy or the custom domain (CNAME) feature (lmk if you do not have a free CNAME yet, I can set you up).
You can find a write up for javascript here:
https://www.ory.sh/nextjs-authentication-spa-custom-flows-open-source/
Hope this helps!