brillout / wildcard-api

Functions as API.
MIT License
368 stars 14 forks source link

Simple authentication (Wildcard sessions) #59

Open brillout opened 4 years ago

brillout commented 4 years ago

The idea is to allow the server to modify the context:

server.login = async function(username, password) {
   if (await invalidCredentials(username, password)) {
     return;
   }

   // `this.user` is persisted
   this.user = {
     username,
   };

   // The user is now logged-in!
};

server.getUserPosts = async function() {
  // `this.user` was set by a previous `server.login` call
  const {user} = this;

  if (!user) {
    // Not logged-in
    return;
  }

  // The user is logged-in!

  const posts = await db.getUserPosts(user);

  // ...
};

~ EDIT ~

What happens here is that Wildcard sets a first cookie with the value of this.user and a second cookie with a signature.

Cookie Name Cookie Value
wildcard_user {"username":"brillout"}
wildcard-signature_user 60a3939232aehua12031389e99d52977e1c282

The signature ensures that the cookie was set by the server.

Wildcard sessions can be used to easily implement any auth strategy:

// Username + password
server.login = function(username, password) {
  if (await invalidCredentials(username, password)) return;
  this.user = { username }; // Wildcard will automatically persist `this.user` by using HTTP cookies
};

// OAuth
server.oauthCallback = function(userProfile) {
   // At the end of the OAuth flow, we save the user information to the context object
   this.user = userProfile; // Wildcard will automatically persist `this.user` by using HTTP cookies
};

// Etc.

I've already implemented a first prototype which I'm currently using in production.

brillout commented 4 years ago

@Johnson444 We can implement this together, if that's something you'd be interested in doing. I've cleaned up the source code, it's now all TypeScript, and test coverage is close to 100% which makes developing super comfy.

michie1 commented 4 years ago

https://github.com/reframejs/wildcard-api/pull/58#issuecomment-711412477

I'm curious, you mentioned a while ago using getContext to set cookies, are you still using that approach? What do you think of #59?

I'm only using it in a small inactive pet project, but yes. Besides putting data in the context from Express, I'm also putting functions in it. It works fine. I think it's nicer to assign the those functions to the context than to the server object. If you read the code you would think the functions are provided by the library, because server is imported from the library.

I would prefer to move Wildcard sessions to its own plugin/library, because I would prefer to handle those things myself, although it's definitely a nice idea to showcase Wildcard and have some authentication running quickly.

brillout commented 4 years ago

The plan is to actually integrate this with an interface that could eventually become a public API. For now the interface would be private but you're right maybe it should be exposed right at the beginning.

brillout commented 4 years ago

I think it's nicer to assign the those functions to the context than to the server object.

Interesting. So basically you prefer to define server endpoints dynamically on each API request, is that correct? Personally, I find it simpler to reason about static functions + a context object. But maybe you are seeing something I'm not seeing.

michie1 commented 4 years ago

I think it's nicer to assign the those functions to the context than to the server object.

Interesting. So basically you prefer to define server endpoints dynamically on each API request, is that correct? Personally, I find it simpler to reason about static functions + a context object. But maybe you are seeing something I'm not seeing.

No, with "those" functions I don't mean the endpoint functions, but the helper functions to interact with Express, like setCookies() or getHeaders().

brillout commented 4 years ago

The getApiHttpResponse() function could be used to control the context:

import express from "express";
import { getApiHttpResponse } from '@wildcard-api/server';

const app = express();

app.all('/_wildcard_api/*', async (req, res) => {
  const context = {
    user: getAuthUser(req.headers),
  };

  const {url, method, body} = req;
  const {httpResponse, endpointResult, endpointName} = await getApiHttpResponse({url, method, body}, context);

  if( endpointName==='login' && endpointResult.success ){
    setCookies(req, endpointResult.user);
  }

  // `httpResponse` contains the HTTP encoded equivalent of `endpointResult`
  res.status = httpResponse.statusCode;
  res.type = httpResponse.contentType;
  res.send(httpResponse.body);
});

This gives full control over to the user.

@michie1 Would that work for you?

I would prefer to handle those things myself

Why? Are you using an Express middleware that does this for you?

michie1 commented 4 years ago

@michie1 Would that work for you?

I would prefer to handle those things myself

Why? Are you using an Express middleware that does this for you?

Different applications need different types of authentication. I think it's better if Wildcard is providing an authentication option (as a plugin) and not enforcing anything. I'm using my own Express middleware function that puts helper functions in the context object.

brillout commented 4 years ago

Wildcard Sessions will be optional and you'll have more control over the context/auth than what currently is the case.

I'm using my own Express middleware

Can you imagine yourself removing your custom Express middleware and use Wildcard Sessions instead? If not, why not? You said you prefer to handle these things yourself and I'm left to wonder why :-).

michie1 commented 4 years ago

Wildcard Sessions will be optional and you'll have more control over the context/auth than what currently is the case.

Cool.

I'm using my own Express middleware

Can you imagine yourself removing your custom Express middleware and use Wildcard Sessions instead? If not, why not? You said you prefer to handle these things yourself and I'm left to wonder why :-).

Well, I might use it for simple applications, but like I said, different applications need different type of authentication. Maybe I don't want to use a password for authentication, but I want have an login link send by email, use 2FA or SSO. It's difficult to compare wildcard sessions that is not there yet versus all the other existing options.

brillout commented 4 years ago

Wildcard Sessions only does session management; you can use it with any auth strategy:

// Username + password
server.login = function(username, password) {
  if (await invalidCredentials(username, password)) return;
  this.user = { username }; // Wildcard will automatically persist `this.user` by using HTTP cookies
};

// OAuth
server.oauthCallback = function(userProfile) {
   // At the end of the OAuth flow, we save the user information to the context object
   this.user = userProfile; // Wildcard will automatically persist `this.user` by using HTTP cookies
};

// Etc.

Instead of dealing with cookies you simply deal with reading/writing the context object.

ggoodman commented 3 years ago

I would suggest that if you're looking to check the authenticity of the cookie, a library like https://npm.im/@hapi/iron is the gold standard for this sort of thing. I see that my colleagues at Auth0 reach for this library for similar uses-cases.

brillout commented 3 years ago

I would suggest that if you're looking to check the authenticity of the cookie, a library like https://npm.im/@hapi/iron is the gold standard for this sort of thing. I see that my colleagues at Auth0 reach for this library for similar uses-cases.

Thanks for the pointer. Wildcard is using HMAC-SHA256 which is old, battle-tested, and assailed against by researchers. So we should be good here.