ChatGPT解决这个技术问题 Extra ChatGPT

SPA best practices for authentication and session management

When building SPA style applications using frameworks like Angular, Ember, React, etc. what do people believe to be some best practices for authentication and session management? I can think of a couple of ways of considering approaching the problem.

Treat it no differently than authentication with a regular web application assuming the API and and UI have the same origin domain. This would likely involve having a session cookie, server side session storage and probably some session API endpoint that the authenticated web UI can hit to get current user information to help with personalization or possibly even determining roles/abilities on the client side. The server would still enforce rules protecting access to data of course, the UI would just use this information to customize the experience. Treat it like any third-party client using a public API and authenticate with some sort of token system similar to OAuth. This token mechanism would used by the client UI to authenticate each and every request made to the server API.

I'm not really much of an expert here but #1 seems to be completely sufficient for the vast majority of cases, but I'd really like to hear some more experienced opinions.


C
Community

This question has been addressed, in a slightly different form, at length, here:

RESTful Authentication

But this addresses it from the server-side. Let's look at this from the client-side. Before we do that, though, there's an important prelude:

Javascript Crypto is Hopeless

Matasano's article on this is famous, but the lessons contained therein are pretty important:

https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/august/javascript-cryptography-considered-harmful/

To summarize:

A man-in-the-middle attack can trivially replace your crypto code with

A man-in-the-middle attack is trivial against a page that serves any resource over a non-SSL connection.

Once you have SSL, you're using real crypto anyways.

And to add a corollary of my own:

A successful XSS attack can result in an attacker executing code on your client's browser, even if you're using SSL - so even if you've got every hatch battened down, your browser crypto can still fail if your attacker finds a way to execute any javascript code on someone else's browser.

This renders a lot of RESTful authentication schemes impossible or silly if you're intending to use a JavaScript client. Let's look!

HTTP Basic Auth

First and foremost, HTTP Basic Auth. The simplest of schemes: simply pass a name and password with every request.

This, of course, absolutely requires SSL, because you're passing a Base64 (reversibly) encoded name and password with every request. Anybody listening on the line could extract username and password trivially. Most of the "Basic Auth is insecure" arguments come from a place of "Basic Auth over HTTP" which is an awful idea.

The browser provides baked-in HTTP Basic Auth support, but it is ugly as sin and you probably shouldn't use it for your app. The alternative, though, is to stash username and password in JavaScript.

This is the most RESTful solution. The server requires no knowledge of state whatsoever and authenticates every individual interaction with the user. Some REST enthusiasts (mostly strawmen) insist that maintaining any sort of state is heresy and will froth at the mouth if you think of any other authentication method. There are theoretical benefits to this sort of standards-compliance - it's supported by Apache out of the box - you could store your objects as files in folders protected by .htaccess files if your heart desired!

The problem? You are caching on the client-side a username and password. This gives evil.ru a better crack at it - even the most basic of XSS vulnerabilities could result in the client beaming his username and password to an evil server. You could try to alleviate this risk by hashing and salting the password, but remember: JavaScript Crypto is Hopeless. You could alleviate this risk by leaving it up to the Browser's Basic Auth support, but.. ugly as sin, as mentioned earlier.

HTTP Digest Auth

Is Digest authentication possible with jQuery?

A more "secure" auth, this is a request/response hash challenge. Except JavaScript Crypto is Hopeless, so it only works over SSL and you still have to cache the username and password on the client side, making it more complicated than HTTP Basic Auth but no more secure.

Query Authentication with Additional Signature Parameters.

Another more "secure" auth, where you encrypt your parameters with nonce and timing data (to protect against repeat and timing attacks) and send the. One of the best examples of this is the OAuth 1.0 protocol, which is, as far as I know, a pretty stonking way to implement authentication on a REST server.

https://www.rfc-editor.org/rfc/rfc5849

Oh, but there aren't any OAuth 1.0 clients for JavaScript. Why?

JavaScript Crypto is Hopeless, remember. JavaScript can't participate in OAuth 1.0 without SSL, and you still have to store the client's username and password locally - which puts this in the same category as Digest Auth - it's more complicated than HTTP Basic Auth but it's no more secure.

Token

The user sends a username and password, and in exchange gets a token that can be used to authenticate requests.

This is marginally more secure than HTTP Basic Auth, because as soon as the username/password transaction is complete you can discard the sensitive data. It's also less RESTful, as tokens constitute "state" and make the server implementation more complicated.

SSL Still

The rub though, is that you still have to send that initial username and password to get a token. Sensitive information still touches your compromisable JavaScript.

To protect your user's credentials, you still need to keep attackers out of your JavaScript, and you still need to send a username and password over the wire. SSL Required.

Token Expiry

It's common to enforce token policies like "hey, when this token has been around too long, discard it and make the user authenticate again." or "I'm pretty sure that the only IP address allowed to use this token is XXX.XXX.XXX.XXX". Many of these policies are pretty good ideas.

Firesheeping

However, using a token Without SSL is still vulnerable to an attack called 'sidejacking': http://codebutler.github.io/firesheep/

The attacker doesn't get your user's credentials, but they can still pretend to be your user, which can be pretty bad.

tl;dr: Sending unencrypted tokens over the wire means that attackers can easily nab those tokens and pretend to be your user. FireSheep is a program that makes this very easy.

A Separate, More Secure Zone

The larger the application that you're running, the harder it is to absolutely ensure that they won't be able to inject some code that changes how you process sensitive data. Do you absolutely trust your CDN? Your advertisers? Your own code base?

Common for credit card details and less common for username and password - some implementers keep 'sensitive data entry' on a separate page from the rest of their application, a page that can be tightly controlled and locked down as best as possible, preferably one that is difficult to phish users with.

Cookie (just means Token)

It is possible (and common) to put the authentication token in a cookie. This doesn't change any of the properties of auth with the token, it's more of a convenience thing. All of the previous arguments still apply.

Session (still just means Token)

Session Auth is just Token authentication, but with a few differences that make it seem like a slightly different thing:

Users start with an unauthenticated token.

The backend maintains a 'state' object that is tied to a user's token.

The token is provided in a cookie.

The application environment abstracts the details away from you.

Aside from that, though, it's no different from Token Auth, really.

This wanders even further from a RESTful implementation - with state objects you're going further and further down the path of plain ol' RPC on a stateful server.

OAuth 2.0

OAuth 2.0 looks at the problem of "How does Software A give Software B access to User X's data without Software B having access to User X's login credentials."

The implementation is very much just a standard way for a user to get a token, and then for a third party service to go "yep, this user and this token match, and you can get some of their data from us now."

Fundamentally, though, OAuth 2.0 is just a token protocol. It exhibits the same properties as other token protocols - you still need SSL to protect those tokens - it just changes up how those tokens are generated.

There are two ways that OAuth 2.0 can help you:

Providing Authentication/Information to Others

Getting Authentication/Information from Others

But when it comes down to it, you're just... using tokens.

Back to your question

So, the question that you're asking is "should I store my token in a cookie and have my environment's automatic session management take care of the details, or should I store my token in Javascript and handle those details myself?"

And the answer is: do whatever makes you happy.

The thing about automatic session management, though, is that there's a lot of magic happening behind the scenes for you. Often it's nicer to be in control of those details yourself.

I am 21 so SSL is yes

The other answer is: Use https for everything or brigands will steal your users' passwords and tokens.


Great answer. I appreciate the equivalence between token auth systems and basic cookie auth (which is often built into the web framework). That's sort of what I was looking for. I appreciate you covering so many potential issues for consideration too. Cheers!
I know it's been a while but I'm wondering if this should be expanded to include JWT? auth0.com/blog/2014/01/07/…
Token It's also less RESTful, as tokens constitute "state and make the server implementation more complicated." (1) REST requires the server to be stateless. A token stored client-side doesn't represent state in any meaningful way for the server. (2) Marginally more complicated server-side code has nothing to do with RESTfulness.
lol_nope_send_it_to_me_instead I loved this function's name :D
One thing you appear to overlook: Cookies are XSS safe when marked httpOnly, and can be locked down further with secure and samesite. And cookie handling has been around much longer === more battle hardened. Relying on JS and local storage to handle token security is a fools game.
C
Community

You can increase security in authentication process by using JWT (JSON Web Tokens) and SSL/HTTPS.

The Basic Auth / Session ID can be stolen via:

MITM attack (Man-In-The-Middle) - without SSL/HTTPS

An intruder gaining access to a user's computer

XSS

By using JWT you're encrypting the user's authentication details and storing in the client, and sending it along with every request to the API, where the server/API validates the token. It can't be decrypted/read without the private key (which the server/API stores secretly) Read update.

The new (more secure) flow would be:

Login

User logs in and sends login credentials to API (over SSL/HTTPS)

API receives login credentials

If valid:

Register a new session in the database Read update

Encrypt User ID, Session ID, IP address, timestamp, etc. in a JWT with a private key.

API sends the JWT token back to the client (over SSL/HTTPS)

Client receives the JWT token and stores in localStorage/cookie

Every request to API

User sends a HTTP request to API (over SSL/HTTPS) with the stored JWT token in the HTTP header

API reads HTTP header and decrypts JWT token with its private key

API validates the JWT token, matches the IP address from the HTTP request with the one in the JWT token and checks if session has expired

If valid:

Return response with requested content

If invalid:

Throw exception (403 / 401)

Flag intrusion in the system

Send a warning email to the user.

Updated 30.07.15:

JWT payload/claims can actually be read without the private key (secret) and it's not secure to store it in localStorage. I'm sorry about these false statements. However they seem to be working on a JWE standard (JSON Web Encryption).

I implemented this by storing claims (userID, exp) in a JWT, signed it with a private key (secret) the API/backend only knows about and stored it as a secure HttpOnly cookie on the client. That way it cannot be read via XSS and cannot be manipulated, otherwise the JWT fails signature verification. Also by using a secure HttpOnly cookie, you're making sure that the cookie is sent only via HTTP requests (not accessible to script) and only sent via secure connection (HTTPS).

Updated 17.07.16:

JWTs are by nature stateless. That means they invalidate/expire themselves. By adding the SessionID in the token's claims you're making it stateful, because its validity doesn't now only depend on signature verification and expiry date, it also depends on the session state on the server. However the upside is you can invalidate tokens/sessions easily, which you couldn't before with stateless JWTs.


In the end a JWT is still 'just a token' from a security standpoint I think. The server could still associate the user id, IP address, timestamp etc. with an opaque session token and it would be no more or less secure than a JWT. However, the stateless nature of JWT does make the implementation easier.
@James the JWT has the advantage of being verifiable and capable of carrying key details. This is pretty useful for various API scenarios, like where cross-domain auth is required. Something a session won't be as good for. It is also a defined (or at least in progress) spec, which is useful for implementations. That isn't to say it's any better than any other good token implementation, but it is well defined and convenient.
@Chris Yes I agree with all of your points. However, the flow described in the answer above is not inherently a more secure flow as claimed due to the use of a JWT. Furthermore, the JWT is not revocable in the scheme described above unless you associate an identifier with JWT and store state on the server. Otherwise you either need to regularly get a new JWT by requesting username/password (poor user experience) or issue a JWT with a very long expiry time (bad if the token gets stolen).
My answer is not 100% correct, because JWT can actually be decrypted/read without the private key (secret) and it's not secure to store it in localStorage. I implemented this by storing claims (userID, exp) in a JWT, signed it with a private key (secret) the API/backend only knows about and stored it as a HttpOnly cookie on the client. That way it cannot be read by XSS. But you have to use HTTPS because the token could be stolen with MITM attack. I will update my answer to reflect on this.
@vsenko The cookie is sent with each request from the client. You don't access the cookie from the JS, it's tied with each HTTP request from the client to the API.
D
DelphiLynx

I would go for the second, the token system.

Did you know about ember-auth or ember-simple-auth? They both use the token based system, like ember-simple-auth states:

A lightweight and unobtrusive library for implementing token based authentication in Ember.js applications. http://ember-simple-auth.simplabs.com

They have session management, and are easy to plug into existing projects too.

There is also an Ember App Kit example version of ember-simple-auth: Working example of ember-app-kit using ember-simple-auth for OAuth2 authentication.