elm-spa
elm-spa copied to clipboard
elm-spa encourages a security anti-pattern of client-side access control
I'll start by saying that I respect the effort of elm-spa and I want to either use it or learn from it for my own Elm projects! I'm not trying to tear it apart here, just point out what I believe is a big security anti-pattern that elm-spa and its guide seems to encourage. So, hopefully this will come across as constructive :slightly_smiling_face:
The front page of the guide states:
Use the
Page.protected
API to easily guarantee only logged-in users can view certain pages.
The User authentication example in the guide further states:
Let's make it so the homepage is only available to signed in users. we only need to change the page function to guarantee signed-in users are viewing the homepage:
Unfortunately, elm-spa itself can deliver no such guarantees. I believe that this guide encourages readers to implement a client-side access control mechanism, where code that controls what a user can and cannot see (based on whether they are logged in and/or who they are logged in as) is served directly to the user's browser, along with the very content that should only be visible to certain users (even if it's hidden by default).
The Page.protected
API on its own cannot effectively control access, because a user's browser is an untrustable environment. A user can inspect and modify any client-side code using their browser's Developer Tools. In pseudocode, you can do if userLoggedIn == True then "secret stuff" else "you cannot see this because you are not logged in"
, but the user could look at the JavaScript source in their browser and (e.g.) change True
to False
, or just look at the secret stuff.
Yes, the user would be looking at minified JavaScript output from the Elm compiler, and that will be harder to read than the Elm source code. But anything that's compiled into the Elm app will still be in there, and any compiled-in strings (e.g. as Html.text
) will still be searchable.
elm-spa is clearly not trying to help people implement server-side authentication/authorization of users, which is fine! But folks should be warned against trying to implement this client-side, and tools like elm-spa should be careful not to mislead people into thinking it's a good idea. The language of "guarantees" feels especially misleading to less-experienced developers.
The typical way for a web app to handle content intended only for certain users is for the server to verify that a user is who they claim to be (e.g. with login credentials sent to the server and a session initiated), and only after that is the sensitive content sent to the user's browser. Perhaps the guide should encourage people to do that, and state that things like Page.protected
should only be used as a secondary convenience layer, and sensitive or logged-in-only content should never be compiled into JavaScript code that is served to unauthenticated users.
Some references:
-
A Field Guide to Static Apps: Authentication and Authorization
-
"Applications must place authorization outside the purview of the client-side code, since it could be arbitrarily modified by any party."
-
- Do Not Rely on Client-Side Validation
- How to perform Login Authentication at the client-side? (Answer: "You can't.")
-
OWASP Authorization Cheat Sheet
-
Developers must never rely on client-side access control checks. While such checks may be permissible for improving the user experience, they should never be the decisive factor in granting or denying access to a resource; client-side logic is often easy to bypass. Access control checks must be performed server-side.
-
Hey @c-mart ,
I totally agree with the security best practices you've outlined above.
To clarify my opinion on how to use elm-spa: The values passed into the Page.protected
pages are usually just the "Temporary Revocable Access Credentials" as described in the article "A Field Guide to Static Apps: Authentication and Authorization" you linked above.
Here's the strategy the author describes:
The client includes the provided token on subsequent requests to the server as a proof of identity, granting the user access to protected resources.
The goal is to include that "provided token" in the User
value passed into protected pages. From there, the server will decide if they have access to backend resources.
The user authentication example from the website is only an introduction to that Page.protected
API, which does not come along with a backend to provide a real token. For a real example of how to use elm-spa with an actual backend, you should check out the elm-spa-realworld repo.
All that aside, the fact that you raised this concern indicates that the elm-spa documentation led you to believe elm-spa recommended client-side authentication.
How would you change the docs to make things more clear? Should I include a more prominent link to the realworld app in the User Authentication docs?
Suggestions and PRs are definitely welcome! 😃 Ryan