leonardoventurini / helene

Real-time Web Apps for Node.js
https://helene.techster.tech
MIT License
37 stars 1 forks source link

Optional Preconfigured Auth Module #9

Open ceigey opened 1 year ago

ceigey commented 1 year ago

Pie in the sky idea:

It'd be nice to have an optional preconfigured auth module, that makes it easy to achieve something similar to what Meteor has.

I think there's quite a few ingredients required for this, and leading packages in the Node ecosystem like Lucia Auth (https://github.com/lucia-auth/lucia) only capture part of the issue. And, even though Lucia is very agnostic (vs Auth.JS), if you move away from the supported middleware the API becomes more complex. But, the idea is quite nice - especially for Mongoose.

Comparing to Meteor Auth, some things that are missing include email and token management. Other frameworks (like Django) go further with admin and migrations, but if this is a BYO DB approach I don't think those should be in scope.

I think roles should be considered separately, and overall this module should not be confused with the default agnostic auth capabilities, nor should Helene be coupled to this functionality.

Edit: I just had a walk and nap and since then though that perhaps it’s better to avoid a fully preconfigured module for this first, and instead have some opinionated utilities for things like tokens - or even just documenting a snippet on how to build your own tokeniser. Then, maybe build up from those more basic elements?

leonardoventurini commented 1 year ago

This would be easier if had a module that couples Helene to MongoDB for example.

The harder approach would be to abstract everything away.

The first approach would require some rework for each database implementation.

The second approach would be more in line with the building the utilities from the ground up approach.

ceigey commented 12 months ago

I will try to make a demo project and mess around a bit with some alternative utilities until I find something nice. I guess all Helene really need to know for Auth/n is:

Some of those might need to run automatically in the context of a session (≈ DDP connection), which might be configurable. Eg. The app might have no need to automatically check if a user is logged in. Maybe there could be middleware that reaches into those hooks automatically, or it can just be documented.

*not sure if too broad or perfect coverage of scenarios. Things included might be:

leonardoventurini commented 12 months ago

Here is how Helene handles it today, agnostically:

https://github.com/leonardoventurini/helene/blob/e47b02a9e9d73ee9ebeb4a60aae905d5a2587ec0/packages/server/src/server.ts#L169

Basically one method for login, and a callback for validating the token.

Everything (expiration, validation, caching) can be handled in one of these two "functions" passed into setAuth.

Not perfect I know, but is fairly simple and effective.

Works for https://sapienzalearning.ai

I even have something akin to passwordless, but I am using Mongoose which I am not very happy with, perhaps we don't need any abstraction beyond mongo itself and something build ourselves.

To have a core accounts module we would need to have basic mongo module encapsulating and standardizing database stuff.

Perhaps this accounts module could be agnostic too, perhaps passing just a simple callbacks for CRUD which would could plug any db.

ceigey commented 11 months ago

Oh nice to see the production site, what sort of token generation are you using?

I noticed in the docs that entrypoint for auth but felt there was still some gaps that might affect newcomers (or people like me with poor discipline who need a bit of rails to keep them focused). The two things in particular that jump out in the docs are:

// auth method - validating a token
if (!isValid(token)) return false

// logIn method - generating a token and checking if the user can login
const token = await Auth.login({ username, password})

The main opportunities I see there are:


Tokens

Everything token related should be achievable by wrapping an existing token or JWT library so that a user just needs to add some defaults e.g. the server secret, expiry time etc.

I'm browsing through the JWT site's library list but not 100% sure which is the best one.

Both https://github.com/auth0/node-jsonwebtoken and https://github.com/panva/jose look pretty legit.

Jose has a nice example for signing JWTs which looks like someone'd only need a thin wrapper over it to get started:

https://github.com/panva/jose/blob/main/docs/classes/jwt_sign.SignJWT.md

I also saw https://github.com/jwtk/njwt which states "nJwt is the cleanest JSON Web Token (JWT) library for Node.js developers". I don't know much beyond that.

Actually, tokens shouldn't even need to contain data (might even be an anti-feature/distraction), so JSON is probably overkill. But everything JWT related has better SEO... 😅


Checking if a user can log in

I was thinking a bit while writing this comment, and without enforcing a particular DB structure etc I think setAuth is pretty optimal, but perhaps the naming or example is misleading, or there's missing opportunities.

E.g.

  // ...
  async logIn({ username, password }) {
    const token = await Auth.login({ username, password})

    return { token }
  }
  // ...

logIn has overlap with any hypothetical validateLoginAttempt, and {username, password} is basically just a Record<'username'|'password'|unknown, string> of claims.

This has a lot of similarities to Meteor's validateLoginAttempt hook, but that accepts a whole attempt object which looks something like:

// My own bad ad-hoc types from a project
type Attempt = {
  methodName: string
  methodArguments: string[]
  user: Meteor.User
  type: string
  connection: unknown // was too lazy to type this
  allowed: boolean
  // and there's some other things if allowed is false I think
}

This is overkill but I especially like the following:

Which could fit into a type like:

type MaybeHeleneAttempt = {
  strategy: 'password' | 'passwordless' | 'otp' | 'twofactor' | 'etc...'
  username?: string
  email?: string
  password?: string
  phoneNumber?: string
  token?: string
}

(or you could put all the claims in a claims object, but in a way the strategy is also a claim in itself, so... I don't think it's a big deal)

I see that logIn doesn't seem to have any types as is, which is maybe ideal: maybe this only needs to be addressed in the docs.


Me being pendatic

Another little thing: in https://github.com/leonardoventurini/helene/blob/e0cca35bd6942863dcb7751c8624ea2fff245f03/packages/client/src/client.ts#L348 there's login but in https://github.com/leonardoventurini/helene/blob/e0cca35bd6942863dcb7751c8624ea2fff245f03/packages/server/src/server.ts#L169 there's logIn (with an uppercase I).

I think making the server-side one into validateLogin with consistent casing with client might be better.

Likewise auth could be named something like validateSession?

But I note that the current version is 1.3.0-alpha.9 so it might be too inconvenient to change that now if people are using Helene already and not expecting breaking changes (maybe they can be aliased to work anyway)

leonardoventurini commented 11 months ago

Wow @ceigey I really appreciate you taking the time on writing such a comprehensive comment. This hans't been top of mind for a while so I ask forgiveness if my reply is too shallow.

Simply enough put there isn't many people using besides myself I think, feel free to change anything, if aliases can be added great otherwise don't worry, feel free to break things!

I use jsonwebtoken and bcrypt for token and passwords respectively for Sapienza Learning, it's a fully custom implementation, also I did some workarounds to support Google Auth, which can be abstracted into a package of course.

I think it makes sense to rename those callbacks, I never particularly liked those names, but it was simple enough at the time. I like validateSession and validateLogin.

I like the types as well, please let me know if you need any assistance in implementing this, of course if you are planning to. Otherwise don't worry.

ceigey commented 11 months ago

On the contrary! I sort of went back and forth with my thoughts so apologies for the rollercoaster ride. I'm happy to contribute a PR, so I'll try breaking things the next opportunity I get - maybe this weekend.