feathersjs-ecosystem / authentication

[MOVED] Feathers local, token, and OAuth authentication over REST and Websockets using JSON Web Tokens (JWT) with PassportJS.
MIT License
317 stars 118 forks source link

The auth cookie monster: server rendered routes, account linking #630

Closed daffl closed 6 years ago

daffl commented 6 years ago

With https://github.com/feathersjs/authentication-jwt/pull/55 I started to tackle a whole host of authentication issues around handling of cookies. Specifically issues around making it easier to render authenticated Express routes on the server and linking multiple accounts together if the user is already authenticated. The issues I could find were:

Rendering Express routes/SSR

Account linking

cookie_monster2

TimNZ commented 6 years ago

Thanks for merging the authentication-jwt PR 55 into master. I had already forked and done the same.

I'm still confused, partially because I am not clear on the express/passport pipeline.

I think I need to explicitly call authenticate('jwt') somewhere on the server in the express pipeline to process the cookie on request of any URL?

Are you still working on the authentication scenarios or was this the only mod?

I've still got the issues here: https://github.com/feathersjs/authentication/issues/627#issuecomment-358514151

Can you please advise where you are at, if more is coming, or what code I need to implement in the server.

TimNZ commented 6 years ago

Also: https://github.com/feathersjs/authentication/issues/495

I expect there are other issues that overlap that you haven't listed.

daffl commented 6 years ago

There is now a recipe showing how to use Feathers authentication with Express middleware (including server side rendering) at https://docs.feathersjs.com/guides/auth/recipe.express-middleware.html

TimNZ commented 6 years ago

@daffl Great support, thanks a lot.

TimNZ commented 6 years ago

@daffl I think the only outstanding consideration is the req.authenticated check preventing authentication using multiple strategies e.g. linking an oauth account to an existing user?

https://github.com/feathersjs/authentication/blob/master/lib/express/authenticate.js#L14

I can't be sure that any details in the oauth provided profile can be deterministically used to link to an existing user e.g. email.

So I have to rely on them already being authenticated via JWT + cookie.

I will do my own checks in my custom Verifier around duplicate email etc.

daffl commented 6 years ago

I haven't checked the account linking yet. I'm thinking we may just have to add the cookieParser and JWT authentication middleware (this is where it might get tricky because we don't necessarily want it to error) but it should be much more easily doable now and then hopefully baked in as a standard features.

TimNZ commented 6 years ago

I now realise I've overcomplicated this. I'll just use the oauth module to handle linking oauth2 accounts to an existing user. You can't allow multiple authenticate() calls to succeed for different strategies in the same request, once someone is authenticated, that's it.

TimNZ commented 6 years ago

I continue to play and learn Feathers, understanding how socket/REST authentication is completely disconnected from cookies/SSR as they go through a different pipeline

If I authenticate via client SDK which returns JWT that is then stored in localStorage (by default).

Using cookie-storage as an alternative on the client to localStorage seems to solve this disconnect between authentication flow. https://docs.feathersjs.com/api/authentication/client.html#options

Manually update localStorage, or other way around (cookies), as you see fit on login/logout to keep everything in sync.

Now just have to decide whether I need to link express sessions with JWT

import {CookieStorage} from 'cookie-storage';

const socket = io('http://localhost:3030');
const client = feathers();

client.configure(socketio(socket));
client.configure(authentication({
  storage: new CookieStorage()
}));
zaro commented 6 years ago

Just spent quite some time , figuring out how to link social account to an existing local account, so just posting it here as this seems the most relevant issue on how I achieved it. Basically I made a custom verifier which overloads verify() like so:

const authentication = require('@feathersjs/authentication');
const cookieParser = require('cookie-parser')();
const authenticateHook = authentication.hooks.authenticate('jwt');

class Verifier extends oauth2.Verifier {
  verify (req, accessToken, refreshToken, profile, done) {
    cookieParser(req, null, () => {});
    if(req.cookies['feathers-jwt']){
        const context = {
            type: 'before',
            app: req.app,
            data: {},
            params: {
              cookies: req.cookies,
              headers: req.headers,
              provider: 'rest',
            }
        };
        return authenticateHook(context).then(r => {
            if(r.params.authenticated) {
               req.user = r.params.user;
            }
            return super.verify(req, accessToken, refreshToken, profile, done);
        }).catch(e => {
            return super.verify(req, accessToken, refreshToken, profile, done);
        });
    }
    return super.verify(req, accessToken, refreshToken, profile, done);
  }
}

If you use localstorage to keep the token on the client, make sure to copy the token to 'feathers-jwt' cookie before the link to '/auth/provider' is triggered.

daffl commented 6 years ago

This is really useful, thank you for sharing!

TimNZ commented 6 years ago

Thanks @zaro! Good timing, as about to restart the app dev. Will plug this in soon and report back.

daffl commented 6 years ago

With cookies enabled and the existing JWT set as feathers-jwt in the cookie, account linking is now possible with @feathersjs/authentication-jwt@^2.0.0 and @feathersjs/authentication-oauth@^1.2.0 so this issue can also be closed now.