Persisting user state in sveltekit - jwt

I'm trying to hook up a Strapi backend to a SvelteKit frontend, and stuck on how to persist user login state so that everything doesn't just reset on refresh, or when navigating to a new page. I've tried:
Storing the jwt and user object issued by Strapi in localStorage and initializing the Svelte store with it. Seemed like I was getting close, but a) I couldn't do export const user = writable(localStorage.user) because that code was running in the browser, and I couldn't wrap it in an if (browser) {...} because import and export can only appear at the top level. Also tried a function in hooks.js to read the contents of localStorage and update the store, but it seems that functions getting called from there run on the server, even if it's the same function that works to access localStorage on login... and plus b) from what I gather, storing jwt's in localStorage is insecure.
Storing the jwt and user object in an http only cookie. Cookies and http headers seem really confusing, and I had a hard time manipulating them to store the jwt and put it in each header. But I think what really stumped me was essentially the same SSR issue of never knowing essentially how to successfully interface between the client and server. I.e. if (browser) {...} never seemed to work, or I couldn't get it to, anyway. (Happy to provide more code details on what I tried here if needed. It's a mess, but it's saved in git.)
I know this is a thing every app that has users needs to do, so I'm sure there's a way to do it in SvelteKit. But I can't find anything online that explains it, and I can't figure it out from the official docs either.
So am I missing something easy? (Probably.) Or is there a tricky way to do this?

For a SvelteKit SPA authenticating to Strapi, here's the happy path flow I would use:
SvelteKit page /routes/login.svelte collects the username (identifier) and password and a button's on:click handler posts those values via fetch to your own /routes/auth.js:post() endpoint. Why? Because now your server's endpoint is handling the login on behalf of the user so you can set an httpOnly cookie in the response.
In your auth.js endpoint post() method, you will want to do a few things:
Use fetch to post identifier and password to Strapi authentication (http://localhost:1337/auth/local) to get the JWT (response.body.jwt)
Use fetch to get user info from Strapi (http://localhost:1337/users/me). In the get, add a header called 'Authentication' with a value of 'Bearer ' + the JWT you just received from Strapi in the previous step
Return the user info from the response from Strapi and pass it back to your client in the response.body
Set a header httpOnly cookie with the value of the JWT.
Back on the client in the last part of the login button's on:click handler, take the user info from res.json() and put it in a writeable store called user or in the SvelteKit session store...
import { session } from '$app/stores'
...
const loginClickHandler = async () => {
...
const fromEndpoint = await res.json()
session.set({ user: fromEndpoint.user })
}
At this point, you have the user information persisted client-side and the JWT as an httpOnly cookie that can't be read or modified by client-side JavaScript code. Each request you make to your own server's pages or endpoints will send the JWT cookie along.
If you want to logout, call an endpoint on your server (/auth/logout) that sets the existing jwt cookie to have an Expires based on the current date/time:
response.headers['Set-Cookie'] = `jwt=; Path=/; HttpOnly; Expires=${new Date().toUTCString()}`
You would also want to clear the user object in your store (or the session store).
The main takeaway for the above example is that your client would never directly talk to Strapi. Strapi's API would be called only by your SvelteKit server's endpoints. The httpOnly jwt cookie representing your session with Strapi would be included in every request to your server's endpoints to use/validate with Strapi's API (or delete if expired or the user logged out).
There are lots of other approaches but I prefer this one for security reasons.

Related

How do I save session cookie from response in flutter?

Let's say I log into an api with my flutter app. In an website, they automatically store the login cookie and then can use it. But in flutter app, how do I store the cookies and session? And how do I pass that into post to let the api know I have a valid login session?
Q: It's making me more confused, I just want the part to extract
cookie and how to use it.
There are a number of complexities, depending on exactly what you ultimately want to do.
But let's assume:
Your Flutter app makes an HTTP request (GET, PUT, etc.)
The server (e.g. your Flask app) returns cookies in the HTTP response (in the HTTP response header).
Let's further assume your HTTP code looks something like this:
Future<http.Response> fetchAlbum() {
return http.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
}
In that case, you should be able to reference the cookies property of the Response object returned from the server.
SUGGESTION: See also these links:
Flutter For Web Cookie/Token Sessions and Authentication
Add Session Support to Flutter with Flutter Session

storing and sending jwt httponly cookie, and csrf token with postman

I have a flask API, with jwt authentication, on a httponly cookie. I installed interceptor, added the domain(with HTTPS) to the list, and enabled the requests and cookies interception.
but still,
how do I make postman send the cookie I got from logging in to the server? usually, with a simple front-end, it just happens, so I didn't think about it.
all the methods I found in postman documentation, including specifying the value with the token, but I don't have it, since I can't access the httponly cookie. (or can I?)
must I access the cookies? can it be done automatically like simply sending requests from the front-end?
any guidance will be appreciated
After a full evening of research, I did two things to make it work -
in the login request, I added a "test" script(a post-request script in postman), with the following code:
const csrf_token = pm.response.headers.get("set-cookie");
const edited_token = csrf_token.split(/[;=]/)[1];
pm.environment.set("X-CSRF-TOKEN", edited_token);
console.log(csrf_token.split(/[;=]/)[1]);
First, I got the cookie from the response, and then used a regex to separate only the token value, and set it as an environment variable. this way, I could add it as a header later, for accessing protected URLs.
The second step was to add a pre-scrit in any request with a protected URL -
in the pre-request tab, I added the following:
pm.request.headers.add({
key: 'X-CSRF-TOKEN',
value: pm.environment.get("X-CSRF-TOKEN")
});
Which only added the same token I took earlier from the "X-CSRF-TOKEN" environment variable and set it to the header.
Mission accomplished :)
I hope it will help others who bumped into this

Cookie based Authentication in FastAPI

I am looking to integrate Cookie based authentication in my FastAPI App. I want the same to work seamlessly with swagger as well.
I want to have a route (eg: /login) which sets my browser cookies. All other protected route uses Depends in the decorator to verify the key present in cookie. How do I get this to work with OpenAPI authorize button?
Important factor here is integration with Swagger/OpenAPI docs auto generated by FastAPI.
You can have a look at the fastapi-users module that implements a cookie-based authentication (it implements other user-management-related stuff as well, so it is worth a look anyway!).
According to the coookie docs:
Configuration
from fastapi_users.authentication import CookieAuthentication
SECRET = "SECRET"
auth_backends = []
cookie_authentication = CookieAuthentication(secret=SECRET, lifetime_seconds=3600)
auth_backends.append(cookie_authentication)
As you can see, instantiation is quite simple. You just have to define
a constant SECRET which is used to encode the token and the lifetime
of the cookie (in seconds).
You can also define the parameters for the generated cookie:
cookie_name (fastapiusersauth): Name of the cookie.
cookie_path (/): Cookie path.
cookie_domain (None): Cookie domain.
cookie_secure (True): Whether to only send the cookie to the server via SSL request.
cookie_httponly (True): Whether to prevent access to the cookie via JavaScript.
cookie_samesite (lax): A string that specifies the same site strategy for the cookie. Valid values are 'lax', 'strict' and 'none'.
Defaults to 'lax'.
Then you can login with a POST request on the /login endpoint and set the cookie on the browser.
I found no info on the auto-OpenAPI integration, but since login is setting the cookie on the browser, you can log in once and then use the API.

Facebook server-side authentication flow: is this the right "code?"

I'm using FB.login on the JS client and want to verify the user's identity on the server. So, the client gets a signedRequest from facebook and sends it to the server. The server splits on the period, and decodes the second part of the signedRequest into a json object.
What should I be using for "code" when I send my server-side request to
https://graph.facebook.com/oauth/access_token?
client_id=YOUR_APP_ID
&redirect_uri=YOUR_REDIRECT_URI
&client_secret=YOUR_APP_SECRET
&code=CODE_GENERATED_BY_FACEBOOK
My decoded json looks something like:
{"algorithm":"HMAC-SHA256","code":"2.AQCPA_yfx4JHpufjP.3600.1335646800.1-5702286|l11asGeDQTMo3MrMx3SC0PksALj6g","issued_at":1335642445,"user_id":"5232286"}
Is that the code I need? Does it need to be B64 encoded? If this isn't the code, what code should I use?
_
What I've tried:
The request I'm trying to use is:
https://graph.facebook.com/oauth/access_token?client_id=295410083869479&redirect_uri=https://squaredme.appspot.com/facebookredirect&client_secret=44f1TOPSECRETbb8e&code=2.AQCPA_yfx4JHpufjP.3600.1335646800.1-5702286|l11asGeDQTMo3MrMx3SC0PksALj6g
but this returns the error:
{"error":{"message":"Error validating verification code.","type":"OAuthException","code":100}}
I can't tell if this is because I'm using a bad code, or what. Noteably, this is running on my local dev server, and squaredme.appspot.com definitely does NOT resolve to my IP. I don't know if facebook checks that or what - I'm assuming I'd get a better error message. Thanks for any direction!
You are trying to somehow combine the two flows together and that's why things don't work well.
When facebook POSTs into the iframe with your app url and a signed request there are two options, the easy one being that the user is already authenticated and then the signed request will have all the necessary data (including a signed request), then you just load the canvas page and use the JS SDK to get an access token there as well, but in this case there's no need to use the FB.login (since it opens a popup and will automatically close it), you can use the FB.getLoginStatus method which won't annoy the user.
If the user is not authenticated then the sign request will be missing the things you need to use the graph api.
You then redirect the user to the auth dialog, and since you are loaded in an iframe you'll need to return a html response which redirects the parent window using javascript, like:
top.location.href = "AUTH_DIALOG_URL";
When the use is done (accepted or rejected the app) he will be redirected to the "redirect_uri" you added as a parameter to the auth dialog.
If the user accepted your app then you'll be getting the "code" parameter in the query string.
You then take the code, exchange it with an access token as you posted in your question, and then redirect the user back to "apps.facebook.com/YOUR_APP".
When the page then loads the user is already authenticated and you'll be getting a full signed request.
I hope this clarifies things for you, recheck the Server-Side flow it pretty much covers it all.
I also had some trouble with that, then I found the solution here in StackOverflow.
There are two kinds of "code" provided by facebook. One comes inside the signedRequest in the cookie generated by the client-side flow. The Facebook's JS SDK handles this codes and get a access token without telling us anything.
The other type of code comes attached as a query to your redirect URI (http://www.yoururl.com/index.php?code=AAAgyiaus...), when you navigate to OAuth URL (server-side flow). With this code, you go to a Token URL and get your access token.
When you are using the server-side flow, you need to indicate a redirect URI both in the OAuth URL AND in the Token URL, and they have to be exactly the same, so a missing slash or a query string can be a lot of problem.
The codes are different from each other. When you use the both things together, appears to be impossible to get a access token using the code that was inside the cookie's signedRequest.
BUT, it is not. The magic is: the code from signedRequest is associated with NO URI, so as long as the redirect_uri is a mandatory field, all you have to do is to pass it blank when you navigate to the Token URL.
So the final solution is: grab the signedRequest from the cookie, parse it in your server to obtain the code, then read the Token URL:
https://graph.facebook.com/oauth/access_token?
client_id=YOUR_APP_ID
&redirect_uri=&client_secret=YOUR_APP_SECRET
&code=CODE_INSIDE_THE_SIGNED_REQUEST
It looks like a hack, so I don't know how long it's gonna work, but it's working right now.

Logging a user out when using HTTP Basic authentication

I want users to be able to log in via HTTP Basic authentication modes.
The problem is that I also want them to be able to log out again - weirdly browsers just don't seem to support that.
This is considered to be a social-hacking risk - user leaves their machine unlocked and their browser open and someone else can easily visit the site as them. Note that just closing the browser-tab is not enough to reset the token, so it could be an easy thing for users to miss.
So I've come up with a workaround, but it's a total cludge:
1) Redirect them to a Logoff page
2) On that page fire a script to ajax load another page with dummy credentials:
$j.ajax({
url: '<%:Url.Action("LogOff401", new { id = random })%>',
type: 'POST',
username: '<%:random%>',
password: '<%:random%>',
success: function () { alert('logged off'); }
});
3) That should always return 401 the first time (to force the new credentials to be passed) and then only accept the dummy credentials:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult LogOff401(string id)
{
// if we've been passed HTTP authorisation
string httpAuth = this.Request.Headers["Authorization"];
if (!string.IsNullOrEmpty(httpAuth) &&
httpAuth.StartsWith("basic", StringComparison.OrdinalIgnoreCase))
{
// build the string we expect - don't allow regular users to pass
byte[] enc = Encoding.UTF8.GetBytes(id + ':' + id);
string expected = "basic " + Convert.ToBase64String(enc);
if (string.Equals(httpAuth, expected, StringComparison.OrdinalIgnoreCase))
{
return Content("You are logged out.");
}
}
// return a request for an HTTP basic auth token, this will cause XmlHttp to pass the new header
this.Response.StatusCode = 401;
this.Response.StatusDescription = "Unauthorized";
this.Response.AppendHeader("WWW-Authenticate", "basic realm=\"My Realm\"");
return Content("Force AJAX component to sent header");
}
4) Now the random string credentials have been accepted and cached by the browser instead. When they visit another page it will try to use them, fail, and then prompt for the right ones.
Note that my code examples are using jQuery and ASP.Net MVC, but the same thing should be possible with any technology stack.
There's another way to do this in IE6 and above:
document.execCommand("ClearAuthenticationCache");
However that clears all authentication - they log out of my site and they're logged out of their e-mail too. So that's out.
Is there any better way to do this?
I've seen other questions on this, but they're 2 years old - is there any better way now in IE9, FX4, Chrome etc?
If there is no better way to do this can this cludge be relied upon? Is there any way to make it more robust?
The short anser is:
There is no reliable procedure for achieving a "logoff" using
HTTP Basic or Digest authentication given current implemenations of basic auth.
Such authentication works by having the client add an Authorization header
to the request.
If for a certain resource the server is not satisfied with the credentials provided (e.g. if there are none), it will responde with a
"401 Unauthorized" status code and request authentication. For that purpose it will provide a WWW-Authenticate header with the response.
A client need not wait for a server requesting authentication.
It may simply provide an Authorization header based on some local
assumptions (e.g. cached information from the last successful attempt).
While your outlined approach on "clearing" out authentication info has a good chance of working with a wide range of clients (namely widespread browsers),
there is absolutely no guarantee that a nother client might be "smarter" and
simply discriminate proper authentication data for your "logout" page and any other pages of the target site.
You will recognize a similar "problem" with using client side certificate based authentication.
As long as there is no explicit support from clients you might fight on lost ground.
So, if "logoff" is a concern, move over to any session based authentication.
If you have access to the implementation of authentication on the server side you might be able implementing a functionality that will disregard authentication information presented with Authorization header (if still identical to what has been presented during current "session) on request of your application level code (or provide some "timout" after which any credentials will be re-requested), so that the client will ask the user for providing "new" credentials (performing a new login).