[HN Gopher] Show HN: Userbase - Auth and E2E encrypted storage i...
___________________________________________________________________
Show HN: Userbase - Auth and E2E encrypted storage in a few lines
of code
Author : j-berman
Score : 48 points
Date : 2021-01-22 07:57 UTC (15 hours ago)
(HTM) web link (userbase.com)
(TXT) w3m dump (userbase.com)
| nickodell wrote:
| >First, the client makes an unauthenticated request to the server
| to retrieve the password salts associated with the username. If
| no user is found, an error is returned to the client. If a user
| is found, the server sends the client the user's password salt
| and password token salt, which the client uses to rebuild the
| password token. The password token is then passed to the server
| for authentication. To prevent brute force password guesses,
| clients get 25 incorrect attempts in a row before the server
| locks the user out of their account for 24 hours (Note we are
| aware this introduces a DoS vulnerability. Our first priority is
| to protect user data. We plan to implement a more sophisticated
| lockout mechanism in the future).
|
| Hang on, so the process of retrieving the salt gives the remote
| client information about whether the user exists? Doesn't this
| mean that an attacker could take a list of possible usernames,
| and confirm which of them are using your service?
|
| Seems like you could return a salt even when the user doesn't
| exist, and that would prevent this information disclosure.
| j-berman wrote:
| >Doesn't this mean that an attacker could take a list of
| possible usernames, and confirm which of them are using your
| service?
|
| Yes, user enumeration is currently possible. While we recognize
| user enumeration is an issue (because users tend to reuse
| passwords from other sites, or don't want to be found out using
| a site), we concluded that properly defending from user
| enumeration by default would have too material of a negative
| impact on user experience, and instead focused on defending
| against potential follow-up attacks by limiting brute force
| login attempts, and recommending that you tell your users to
| use a password manager at sign up.
|
| The most significant place defending against enumeration
| affects is during sign up. When a user's account already
| exists, we say the username is already taken, which isn't
| possible when properly defending against enumeration.
|
| We're planning to allow you to enable email verification in
| your app if you want to, so users will need an email to
| successfully create an account. Once that's in place, we'll
| defend against enumeration more concretely. There are other
| places in addition to the salt retrieval that would be modified
| in similar fashion. For example, password reset will need to
| always successfully return even if a user provided the wrong
| username, and sharing a database with another user will always
| successfully return even if the other user doesn't exist (e.g.
| from a typo).
| zelon88 wrote:
| WordPress works the same way and it's awful. No offense. They
| don't see the impact of allowing user enumeration.
|
| Security isn't about one feature. It's layered. You need to
| have layers because there is no such thing as guaranteed
| security.
|
| Bank safes are my favorite analogy. Safes are given a time
| rating. "How long can this safe resist being broken into." A
| bank with a 15 minute safe means that it might take an
| attacker 15 minutes to open the safe.
|
| A 15 minute safe is not secure. Infact it is guaranteed to be
| compromised past 15 minutes. How do you secure an insecure
| 15m safe? With a 5m guard duty. Now you have a safe to buy
| you 15 minutes and a guard to ensure that nobody has 15m
| worth of access to the safe.
|
| You built a safe with no guard... and by allowing enumeration
| you're telling attackers where you put the safe. You are
| almost guaranteeing someone will compromise it eventually.
|
| Security doesn't always mean that successful attacks are
| impossible. Oftentimes security just means you've made the
| cost of intrusion higher than the return on investment. If
| you allow enumeration you're giving the attacker an
| advantage.
| j-berman wrote:
| >You built a safe with no guard... and you're telling
| attackers where you put the safe. You are almost
| guaranteeing someone will compromise it eventually.
|
| Userbase is built on the assumption our entire database and
| server will be compromised, and the attacker would still
| not be able to access protected user data. Validating that
| we protect user data in that scenario was the goal of our
| security review. [1]
|
| And we recognize the impact of allowing user enumeration.
| We will offer protection from user enumeration for those
| who are comfortable with the tradeoffs in user experience.
|
| [1]: https://userbase.com/announcements/#1-security-review
| zaroth wrote:
| I know this goes contrary to "best practice" but I am very
| much in favor of this approach.
|
| You want to focus on implementing good soft and hard rate
| limiting on all your endpoints.
|
| You can obfuscate the login function to return an unhelpful
| error message, but unless you harden every possible public
| API against user enumeration -- and most sites do not - you
| are just hurting the UX for no actual security gain.
|
| This would include constant timing for returning results when
| there is or isn't a user, so for example, running your hash
| function against a dummy password.
|
| Years ago there was a big push to return unhelpful error
| messages, but then the signup or password reset functions
| would act as a user exists oracle anyway. Login got harder
| for zero actual gain in security.
| jamescampbell wrote:
| The pricing model is amazing. Either free up to 100 users or a
| modest under $100 USD per year for unlimited. Perfection.
| d33lio wrote:
| Wow this is awesome!
| alexobenauer wrote:
| Big fan of Userbase here -- I started using it just under a year
| ago when it launched, and have built a number of different things
| with it.
|
| It's incredibly simple, which is its main draw for me (besides
| being able to offer e2e encryption to users, which is a huge win
| for privacy).
| j-berman wrote:
| Hi HN,
|
| Userbase is a tool for developers to build secure and private
| apps. We launched 1 year ago [1], and have worked hard to widen
| its use cases. Userbase offers built-in user accounts and
| authentication, an end-to-end encrypted zero-management database,
| file storage, streaming and sharing, and logic to process and
| manage subscriptions (via Stripe). All Userbase features are
| accessible through a simple JavaScript SDK, directly from the
| client. 100% open source, and all platforms are supported
| (browser, iOS, Android, desktop).
|
| Today, Userbase can be used in apps that don't require end-to-end
| encryption, for those who like that Userbase can handle
| authentication, data storage and real-time syncing in a few lines
| of code.
|
| We also completed a security review by an independent team [2],
| and wrote up a comprehensive specification of our architecture
| [3].
|
| Personally I joined in working on Userbase to store end-to-end
| encrypted data in a performant way for an accounting app. Under
| the hood, each write to a Userbase database is an append-only
| transaction to a log stored in DynamoDB (therefore constant
| time), which is then pushed to connected clients over a Web
| Socket. Each client then decrypts and applies this transaction to
| its local state of the database in memory (real-time syncing is
| provided out of the box). In this process, the server ensures
| each client receives transactions in a consistent order, 100% of
| the time. This is unlike some of the (very awesome) decentralized
| alternatives that exist today (OrbitDB, GunDB, Scuttlebot), which
| generally rely on CRDTs to stay in sync, and CRDTs can be pushed
| in any order. For certain applications, the consistent ordering
| guarantee a central server provides may be extremely useful (such
| as in an accounting app), on top of the added reliability and
| performance.
|
| [1] https://news.ycombinator.com/item?id=22145168
|
| [2] https://userbase.com/announcements/#1-security-review
|
| [3]
| https://github.com/smallbets/userbase/blob/master/docs/userb...
| jamescampbell wrote:
| I had a similar setup using redis and zsets so this type of use
| case and implementation makes sense to me.
| jamescampbell wrote:
| I also love the security write up.
| j-berman wrote:
| Thanks for all the kind words :)
| ajconway wrote:
| How do you deal with updating the local database when a client
| was offline for an extended period of time and missed a lot of
| transactions?
| j-berman wrote:
| openDatabase loads the database's state into memory from the
| server, and then keeps it in sync with the server using the
| Web Socket. When you insert a transaction via one of the
| database operations, the server assigns the transaction a
| monotonically increasing sequence number, and broadcasts the
| transaction along with its sequence number out to the clients
| connected to a database. Clients then apply transactions in
| sequential order to the local state, and keep track of the
| latest sequence number applied. When a client goes offline
| and comes back on, it automatically reconnects the Web Socket
| and re-requests any transactions that it may have missed
| above its currently applied sequence number. We handle
| reconnection logic automatically under the hood, retrying on
| failure with backup delays.
|
| Can read more on this process and how we optimize it when
| databases get large here: https://github.com/smallbets/userba
| se/blob/master/docs/userb...
|
| I'm also working on an offline-first Google docs alternative
| that will write to IndexedDB, and stay in sync with Userbase
| using CRDTs. The tutorial on how to do it will be here:
| https://userbase.com/docs/
| ajconway wrote:
| What happens when a new client joins? Does it download the
| entire history of all transactions and replays them into
| the local database?
|
| Hot do concurrent modification get resolved (several
| clients try to modify the shared stage at the same time)?
| j-berman wrote:
| >Does it download the entire history of all transactions
| and replays them into the local database?
|
| This is what clients do initially, until the database
| grows in size. Every time the transaction log increases
| 50 KB, the client takes a snapshot of the database's
| state at a particular point in time, compresses and
| encrypts it, and uploads this state to the server. We
| call this a "bundle". This way when clients reopen a
| database, they load from the bundle first, and then apply
| any new transactions that come after it. Rather than
| needing to query for the history of all transactions and
| decrypting them individually and reapplying.
|
| >Hot do concurrent modification get resolved (several
| clients try to modify the shared stage at the same time)?
|
| The server assigns each transaction a distinct sequence
| number via an atomic operation. So clients always apply
| transactions with the same distinct sequence number, in
| sequential order. The client relies on this to enforce
| uniqueness and versioning. Only the lowest sequence
| number itemId gets applied to a database if 2 clients
| insert with the same itemId at the same time, and
| similarly, only the lowest sequence number version of an
| item gets updated or deleted if 2 clients update or
| delete the same item at the same time.
|
| With regards to bundling, it's a bit more complicated and
| there are layers to our approach in safely handling it
| under high concurrency. When a client uploads a bundle,
| the database records what sequence number the bundling
| took place at so clients can use it to retrieve the
| latest bundle. And the server retains copies of bundles
| at prior sequence numbers. This way if two clients
| attempt to open a database right around the moment a
| bundling process completes (client 1 receives a bundle at
| lower sequence number, and client 2 receives a bundle at
| a higher sequence number), both clients receive the same
| set of transactions regardless. The server sends all
| transactions in the log after the bundle sequence number,
| so client 1 just needs to decrypt and apply more
| individual transactions to rebuild the state compared to
| client 2.
|
| Some may find this interesting too -- we specifically
| test for safe concurrent behavior across 2 clients using
| a makeshift testing framework that opens 2 browsers at
| the same time and does some neat tests:
| https://github.com/smallbets/userbase/tree/master/test
|
| If you clone the repo and run `npm run test:concurrency`,
| it will run those tests and output test results to the
| consoles of the 2 browsers.
___________________________________________________________________
(page generated 2021-01-22 23:01 UTC)