-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ADDED] Cookie JWT auth for WebSocket #1477
Conversation
@pas2k First I need to congratulate you on the quality of this PR. Your included test touches areas that are not so easy to grasp and you have made use of all the existing JWT test helper functions. I need to look more into this approach, but I have reservations on how you use isClientAuthorized() in createClient since this is something normally invoked when receiving a CONNECT, etc.. I wonder if it would not be cleaner and safer to extract (make it a function) the jwt specific authentication part so it could be called here. Again, will look more into it. |
@kozlovic Thank you for the quick review and kind words. A couple of thoughts regarding isClientAuthorized() use, since it is certainly the most contentius change in the PR. Checking JWT changes both server and client state. The server might pull new accounts from account server, for example. So extracting JWT checking logic won't decrease the mutex use. It can eliminate some checks for empty fields, certainly, but that's barely noticeable. Separating JWT checking from isClientAuthorized() can certainly be unsafe, though. Since isClientAuthorized() is used on config reload, there's space for subtle auth bugs if there's a mismatch between JWT-only auth and the rest of the system. Keeping the two synchronized will become a maintenance burden. Seeing how it's used in reload logic, isClientAuthorized() seems to be the only function idempotent against the client auth fields and configuration, and that's why I decided to use it. |
@pas2k With @aricart we tried few things and here are our observations: We used https://github.com/aricart/natsws-sandbox to test and made small modifications to
Run the NATS Server with your PR and a proper config and started a webpage on If we remove setting the cookie in
Then the NATS Server does not see this cookie, and so does not work. If we remove "HttpOnly" from the command above, then it works, NATS Server see cookies and authenticate based on that. The point is that in no case we see "HttpOnly" either masking the jwt in the client or its use in the client prevents the NATS Server from seeing it. Are we missing something? |
Just tried it. It's working as intended. Here are my results:
When you say "looking at the cookie for this page", you probably mean looking at the cookie list in the developer tools, right? HttpOnly cookies are displayed there, but are inaccessible to any client JavaScript. Here's an example attack that this prevents:
Same can be done by a malicious user.
XSS is still the most exploited website vulnerability, so having this feature closes a huge attack surface. |
Yes absolutely this is awesome. I am going to augment the authentication example that I have that uses WSS to include this. |
Yes, and on the lock icon in address bar, then Cookies, etc.. Thanks for all the details. Now that we have established the validity and usefulness of that, I will comment on the code specifically. Stay tuned. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes this LGTM. Soon we'll have the ability for the client to verify identity by signing. When we have this on the client, access to the JWT won't jeopardize it, as we'll rely on the nkey signature. Awesome on the tests.
@aricart If the website's script can sign the auth request, that means it has access to the private key. That also means that any malicious script can acquire the private key the way genuine script did. Any protection against XSS always has to rely on something that JS can't access. Having an ability to sign something with privkey obviously exposes it to XSS, but it is a much less of a problem, since you can't derive a JWT from privkey because JWT is signed with Account key, and without the ability to log in, the privkey is kinda useless. |
Dialing it back a bit:
If we had access to private keys, we can still be fairly creative, even if HttpOnly is not supported. The server could create a one-shot user in the context of an account with a specific limit of a single connection, the authentication process would require the private key for verifying it. The client would by default be isolated, and services would be specifically scoped to the client. On session completion, the account invalidates the user jwt, making any kind of replay impossible. |
@philpennock would probably have additional comments on the strategies, as he's our security expert. |
@aricart This is getting a bit off-topic, which is mostly my fault. It would be nice to have a repo with best practice configuration you described and see how it compares. If you ever do, feel free to ping me, would be happy to discuss it there. |
158b971
to
43b51b7
Compare
Implemented @kozlovic's suggestions, rebased vs master, covered options and protocol JWT override with tests. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very close! Just one obsolete comment and I think that we could alter a bit the way we reference the ws.cookieJwt (see comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Thanks for this contribution and your patience.
When authenticating via browser, the best security practice is to use HttpOnly cookies, to avoid scripts stealing client identity via XSS.
HttpOnly cookies can't be accessed by JavaScript, making those perfect for server-certified authentication.
PR implements optional JWT auth using cookies by specifying this option in a config file:
ws: { jwt_cookie: "cookie_name" }
This will make NATS treat a cookie named "cookie_name" as JWT for authentication. If this cookie exists in the HTTP request and is valid, the server will act as if authentication is not required. If this token is missing or invalid, it's possible to authenticate using other configured methods.
Notice that since signing is not possible when authorizing this way, BearerToken should be set in the JWT (e.g. User should be created with
nsc add user --bearer
).Tests for regular JWT auth and cookie auth are included.
/cc @nats-io/core