I made a small website in Rust. I'm learning Rust because I want to use it to create webapplications. I'll be expanding and improving this site each time until I feel confident enough to use Rust for real.
I'd like some feedback to improve my code/methods of doing things.
And I have a question as well. Normally in PHP I kinda write my back-end and front-end as 'one piece'. With <?php echo htmlspecialchars("..."); ?> I can manipulate the front-end directly from my back-end.
But in Rust I have separated the front-end and back-end totally. In the front-end I use AJAX to make POST/GET requests to my back-end Rust code and when required I put the response in the front-end using Javascript. Is this the right way of doing things? Kinda like an API?
Also, how would I implement sessions? Now when the user logs himself in I check if the password matches with the hash. in PHP I'd set a $_SESSION variable to keep the user logged in. But how would I do that in Rust? I can set sessions using Actix's Session::set but the front-end doesn't remember this because it's separated from the back-end.
I'd have to send some kind of request each time to check if the user has a session? Or how would I do this?
Regarding sessions, PHP implements them by setting a cookie with a random key into some hash map of sessions. The cookie is then sent for every connection.
So I'd implement it by making a hashmap in my back-end Rust-code like <user_id, session_cookie_string>. And in my front-end I'd set a cookie using Javascript. Then in each GET/POST request I send the cookie_string saved by JS in the browser to authenticate the user?
Cookies set with JavaScript can be as easily read with JavaScript. Cookies set with response headers, on the other hand, can have the HttpOnly flag, which restricts their usage to consequent requests.
Cookies are exchanged between server and browser in fields of the HTTP header of requests and responses. The browser takes care of returning cookies, no Javascript required. In fact for the sake of security it is wise to prevent Javascript in the page from reading the cookies, use the HttpOnly attribute on the cookie header.
All nicely described here:
Messing with all this is tedious, better to use a library/framework to take care of it all. For example with Rocket it just comes down to this:
#[get("/logout")]
fn logout(mut cookies: Cookies) -> Option<String> {
if let Some(cookie) = cookies.get_private("user_id") {
println!("Cookie: user_id: {}", cookie);
}
cookies.remove_private(Cookie::named("user_id"));
Some("Successfully logged out.".to_string())
}
// Add the `user_id` cookie on logging in
#[get("/login")]
fn login(mut cookies: Cookies) -> Option<String> {
cookies.add_private(Cookie::new("user_id", "New User"));
Some("Successfully logged in.".to_string())
}
// Retrieve the user's ID, if any.
#[get("/user_id")]
fn user_id(mut cookies: Cookies) -> Option<String> {
match cookies.get_private("user_id") {
Some(c) => Some(format!("User ID: {}", c.value())),
None => Some("User ID: None".to_string()),
}
}
Of course what you do with the cookies and your user sessions is up to you then.
@ZiCog But for this project I have two parts: Server-side (Rust) and client-side (JS, HTML,...). They are both on a different server/domain/ip. The client-side can't contain any server-side code and only communicates with the server-side using GET/POST requests. Would your example still work?
And what I don't understand, if you write cookies.add_private(Cookie::new("user_id", "New User")); how do you check in the front-end like:
//pseudo
if (auth_cookie_is_set) {
goto_panel()
} else {
goto_login()
}
So if I send a POST/GET-request to my server, it will respond with a header containing the cookie-string?
I don't understand it... I need to do an if-statement somewhere to check if the user is authenticated, no? Where would I place that if-statement and what exactly would I check?
There are basically two common ways to establish sessions:
Use a session id cookie; a good quality random buffer. This is essentially your only option if you don't have private cookies.
The server stores its data in private cookies, which are encrypted and authenticated so the client can't manipulate them.
So what does this mean in practice? Well, you don't store a cookie which says user_authenticated=true, user_id=42, etc with the intention to use them both on the client and the server -- because this would be trivial to manipulate the user.
If you use a session id cookie, then make sure the a session has been established first. Create a REST interface that allows a user to authenticate using a username and a passphrase. If the username and passphrase are ok (check against database, etc), then mark the session as "logged in to user X". Now each time a call is made to the server, you get the session id and you check it against your session database. Then on the server you can see if the user is logged on.
If you have private cookies you can actually store cookies which say user_authenticated=true, but note that it will not be available to the client. You still need to make a call to the server which can return the status of the user_authenticated value to the client.
So the server is the one responsible for actually keeping track of user sessions. The client can ask the server whether a user is logged on (using a session id, or private cookies), but the client is passive and just accepts what the server tells it.
Alright. So if I do session.set(...) in Actix-web. Actix will automatically know that all requests from that client are bound to that session?
I already have a REST-interface to login. When the user sends the login-form my server-side code checks if the username and password are correct. Than I do session::set if it's correct.
I haven't used actix-web myself, so I'm not sure how it works. But basically for each call you need to do this (this is assuming you use a session id and have a session database):
Attempt to get session id cookie from client
If client sent a session id cookie, check if it is known (in server's database).
If client didn't send a valid session id, then generate a new session id, and store it and any related data in the local database.
Return the session id to the client using a session id cookie
If you have this you can make the client's calls to the server stateful, and the client can not manipulate the internal state of the server.
So next thing is to use your login rest call.
The login request arrives together with the session id cookie. (Perform the check as above)
If the username an passphrase are ok, then make a note in the server's database that the session associated with this session id is a "logged in" session, and it is logged in with the username passed in as an argument
Any following call to the server from the client is done as in the earlier sequence, but now you can also check the server's database is the session is "logged in" and which user it is associated with.
Now let's say you have a table that should only be seen as an authenticated user. In the server's HTML code it adds a <div id="secret_table"></div>. A javascript snippet is launched on the same page which performs an "ajax" (I'm way too out of the loop to know what it's called nowadays) call to the server and requests /rest/give_me_secret_table. The javascript will pass the session id along with the request. Now, the rest call performs both checks as described above (using the server's local session database), and if the session is a logged in session, it returns the table in json (or whatver) format. If the session is not logged on it can return an error. The javascript on the client will either receive the authenticated-only table data, and can proceed to tell the browser to render it, or it will display an error. Note that the important detail here is that if it's not a user session then the server must not send the secret table data.
This is basically the entire chain used to get authenticated web sessions, and how you protect data behind user authentications and permissions. And also note that things work slightly differently if you use the private cookies method instead, but the basic principle is the same: The server is always responsible for access checks, and doesn't send authenticated data unless it has made sure the session is authenticated.
I'm glad you spelled that out so nicely there. I was just contemplating this whole problem. I had done all this years ago using node.js and react and an authentication service. But that was so long ago and, shall we say, I became a bit rusty on it.
So in short we need to manage the sessions first, with the cookies and session database. Then we can layer the log-in/log-out on top of that.
I think I can see how to do the first three of these steps with warp (the framework I use), but I don't know how to do the last. Do you just have to add an http header appropriately formatted?
I would recommend using a library for generating the cookies. I haven't used Warp -- it it minimalistic? Setting cookies is something frameworks tend to help you with, but it's strictly not necessary, so if it strives for minimalism then yes, you just add it as a header field.
Thanks A LOT for your very clear reply! I only don't know on how to implement step 1 each time.
Attempt to get session id cookie from client
So the client already has a session_id cookie? Where is it stored? Just in document.cookie?
The login request arrives together with the session id cookie. (Perform the check as above)
So I generate the session-id client-side and send it to my server than? The server puts it in the database together with the id of the user that just logged in?
No, it will be present after you (as a web server) set it to the value you need. So, you have to handle the case when it is not there.
In raw HTML request cookies are send in the corresponding header. Warp might give you something to parse it.
No. You generate session ID on the server and send it to the client along with the login form. And then the data from this login from will have this ID attached to them.
Ideally your Javascript in the browser cannot access the cookie. This is a security measure, such that rogue JS introduced into your browser by whatever means cannot steal the cookie and hence use it to pretend to be a valid user. This is why the server sets the HttpOnly attribute on the cookie header in it's response.
Without the HttpOnly attribute the JS in the browser can access the cookie, via document.cookie I believe. This is not good practice.
No. The server generates the session-id. Only the server knows what sessions are going on and who is who.
The server needs to remember all the sessions going on, using the cookie, in a database or wherever. The user has no say in that. But if the user logs in successfully then that session can be associated with a user.
I understand the server-side explenation code for 100% I think. But I really don't get the client-side.
For example:
But then
Ideally your Javascript in the browser cannot access the cookie.
I still don't understand how to check if the user is authenticated from the client-side. Because it's told that Javascript shouldn't have access to the cookie?