sysgears / apollo-universal-starter-kit

Apollo Universal Starter Kit is a SEO-friendly, fully-configured, modular starter application that helps developers to streamline web, server, and mobile development with cutting-edge technologies and ultimate code reuse.
https://apollokit.org
MIT License
1.68k stars 323 forks source link

Add authentication and authorization support #103

Closed sakhmedbayev closed 7 years ago

sakhmedbayev commented 7 years ago

Thank you guys for this starter kit. It is awesome!!! Would be nice to have authentication and authorization support. What do you think?

mitjade commented 7 years ago

I think it would be great.

What I currently use is client certificate on Nginx and proxy to my local app. In case this is something that would work for you, all I needed to do was:

src/server/middleware/graphql.js

...
export default apolloExpress((req) => {
  let serial = '00';
  if((req.headers.serial)) {
    serial = req.headers.serial;
  }

  return {
    schema,
    context: {
      Serial: serial,
      ...
    },
  };

@vlasenko Maybe we could add an example of actual authentication and authorisation as well?

larixer commented 7 years ago

@mitjade Yes, we could. But this topic is very wide. What authentication scheme we should support and using which implementation?

mitjade commented 7 years ago

Maybe we can get some suggestions or wishes from people like @sakhmedbayev that are interested in this topic. Will investigate a bit and make my suggestion what I would like to see being used, since I have not jet needed to deal with this, beside the method mentioned above.

sakhmedbayev commented 7 years ago

Maybe to include basic local authentication with email and OAuth 2 with github???

swhamilton commented 7 years ago

I have needed authentication/authorization and have spent two weeks studying the different approaches with the goal of implementing in this boilerplate.

It is a huge topic with a lot of different implementations so let me give you the summary from my research:

Goal:

Here's what I came up with:

I have a basic PoC doing this in a very simple Express app with the following libraries:

    "passport": "^0.3.2",
    "passport-local": "^1.0.0",
    "express-jwt": "^5.1.0",
    "jsonwebtoken": "^7.3.0",

I am now ready to fork this repo and post a branch that implements this flow. Let me know if you have any suggestions or comments! Security is a beast.

larixer commented 7 years ago

@swhamilton Very interesting research on security, thank you very much for your work! Adding authorization and authentication to this starter kit is a very nice addition on one side. From other side if we make this kit too complicated we will scary away potential users of the kit. So we need balanced approach when essential features are added to the kit but at the same time can be easily removed when the user of the kit is not interested in them.

mitjade commented 7 years ago

105

sakhmedbayev commented 7 years ago

I think authentication and authorization fall under the category of "bare minimum", at least in my opinion :)

mitjade commented 7 years ago

I think that we can all agree information like this is invaluable and to have an example done for this starter kit, would be really helpful. Personally I rather spend a day removing unneeded code, than finding a working solution for two weeks. All we need to agree is, in what way to add this kind of solutions.

For me even if it sits in a un-merged PR, that nobody keeps up to date, is still better than not having it at all. We can discuss possible solutions on #105.

GiladShoham commented 7 years ago

Hi guys, I already did an integration with those libraries: apollo-passportjs apollo-passport-local-strategy apollo-passportjs-react

They are all libraries I have forked and added a lot of features. I'm using it in production along with this starter kit, so I can create a PR for this. It's a lot of work (especially in documentation and maybe some email integration for account verification). So I want to make sure you think it's a good idea before I start working on this.

There is also js-accounts which suppose to be a very flexible and robust solution, but I don't think it's ready yet.

Let me know what you think. Thanks.

larixer commented 7 years ago

I would love @GiladShoham and @swhamilton to proceed, maybe have two implementations in different forks and then merge the best approaches from both implementations, or perhaps you guys could coordinate with each other and work out the best way to proceed?

GiladShoham commented 7 years ago

Thanks @vlasenko @swhamilton I have implemented most of the things you mentioned and some more (like account verification by email) in my libraries. Some I implemented differently, for example storing the token in the local storage instead of cookies. I have also implemented special HOC component for authorization which connects to the Authentication layer and knows to redirect you from an unauthorized route to any route you define. Or hide specific components on the page (for example remove items from a menu for non-admin users) I can integrate this as well if you want. (The redirection now works with react-router 3 but I don't think it will be too complicated to make it work with v 4 as well)

@swhamilton please take a look at my libraries and let me know what you think.

swhamilton commented 7 years ago

thanks for feedback guys! @GiladShoham Your repos look great. Your solutions are definitely more built out than mine, which are just manually implementing the OAuth 2.0 resource owner flow.

I am familiar with your solution already when I did initial research on apollo/graphql auth. I wasn't clear on the state of the project or the plan for future work so I then investigated js-accounts, which also offers some other great features but was not quite ready.

I chatted with several guys from js-accounts on implementation details and plans. They are actively working on it now and you should definitely see what's happening there...check out https://apollodata.slack.com/ channel #js-accounts.

That said, because there were many different approaches to authentication and apollo is still relatively new for me, I went down the path of making a bare-bones PoC so I could draw my own conclusions where I believe different concerns should be handled. Now I'm seeking to integrate with this boilerplate specifically.

The first major issue I ran into was achieving authenticated/authorized SSR on initial load. I have not yet implemented this in my PoC but I know the only way to do it is with a cookie (otherwise the server can't auth you and send you a rendered page with fully hydrated store). Check out this comment for more details: https://github.com/erikras/react-redux-universal-hot-example/issues/608#issuecomment-160167761

Another concern is some people have issues with storing the JWT in localStorage as it is easily accessible for malicious code to use it for XSS. Not a deal breaker for me but something to be aware of.

As far as implementation design, I like that you have a special HOC component for redirection, makes total sense to me to include that in an auth solution. Also, getting apollo-passportjs to work with React router 4 would definitely be the right move as more ppl start using it.

I think there are 3 different paths for integration with this boilerplate:

  1. Don't use preexisting auth framework (like Passport or Grant), write new lib to handle authentication - Don't do this.
  2. Use PassportJS/Grant for auth (local, Facebook, etc), leave rest of implementation details to application - Good for anybody needing control over all the moving parts.
  3. Use PassportJS, Write custom lib for abstracting apollo/passport (you did this ;), integrate with boilerplate (Would be awesome to share it if possible!)

I landed on 2 because I wanted to know what was needed to make a working solution.

Along the way, I figured out that Passport/OAuth2orize did not have any strategies that implemented JWT storage in a cookie the way I wanted, so I ended up writing the code to handle the JWT creation, storing, refreshToken checking, etc. It is very hard-coded and I'll publish it this week. I could have forked OAuth2orize to add the cookie part but that was really overkill for what I needed.

I think your work is a great starting point and using Passport makes the most sense as the strategy approach offers the most flexibility. There are always some who want plug-and-play auth (js-accounts...once it's ready) and others who will need more granular control over the auth flow.

A few resources I found helpful in learning about JWTs, OAuth 2.0, etc: http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/ https://zapier.com/engineering/apikey-oauth-jwt/ https://blog.jscrambler.com/implementing-jwt-using-passport/ https://blog.hyphe.me/using-refresh-tokens-for-permanent-user-sessions-in-node/ https://github.com/FrankHassanabad/Oauth2orizeRecipes

swhamilton commented 7 years ago

I'l revisit your work too. Let me know if you have any ideas how to achieve SSR/auth with apollo passport. Would love to contribute where I can!

GiladShoham commented 7 years ago

@swhamilton This is a very detailed comment. I'll try to follow everything.

About js-accounts, I'm sure it will be a great project. I'm also a member of their slack group, and I know few of them personally. They asked me to migrate some of my code to their solution a few weeks ago, but I didn't have the time to do it yet. Regarding integration for this starter kit, it's still too early for now.

About the SSR problem, actually I ran into this problem as well, it's not an easy one. Because of lack of time, and the fact that I didn't really need the SSR, for now, i just disabled it on my project. Anyway, i think you are right about the cookie and SSR, my solution currently supports local storage only, but we can improve it to support cookie as well and make it configurable.

About the right place to store the token - cookie/localStorage, there is a debate about it. I think none of them is a clear winner.

About the HOC Authorization component, I created a gist for you to see how it works. (Wrote the examples right away without any testing, so sorry if there are silly mistakes, it's just a demo)

About 3 paths for integration, I think to reinvent the wheel (option 1) as you said, don't do this. Option 2 - I don't completely understand what does it means that the boilerplate will do for the user? only install those libraries? if there is no added value for the user it's pointless. Option 3 - is a good option, I think for now my solution is the best for this stack (apollo, react, graph), and it's already working on my env with this starter kit (before react-router 4).

I think we should start to integrate it, as start without cookie and SSR support. (Still better than nothing). Then later add the SSR support.

I won't have much time for this, this week, but I'll have time for helping if you want to start with this. I can share my code but it will require some changes to make it a bit more generic and to extract it from other libs/tools I use in my project.

Let me know how you want to proceed.

Have a great day!

swhamilton commented 7 years ago

@GiladShoham If you're up for making this something that everybody can use I think we can do it.

You have more experience than me with your apollo-passport extension work so I will try to use your work as a starting point.

To ensure our work is successful we need to gather our requirements/design goals.

Here are some of my guiding params for the project:

  1. Don't reinvent stuff, use other packages/work as much as necessary.
  2. Don't create something that has multiple concerns. Break into separate packages if necessary.
  3. Don't force lock-in to this solution (goes with 2)
  4. Start small and keep code as simple as possible. (should be Reasonaboutable, so others can contribute without knowing everything)
  5. Needs to have tests written.

Some questions:

  1. Do we use apollo-passport as a starting point even though it's technically deprecated, or do we take a step back and use only Passport (without apollo-passport) and create a new library/strategy to handle integration with this boilerplate?
  2. Do we add a basic dummy db (just a file with update/save/delete functions and in-memory arrays) for handling any type of token storage and let the community create DB drives (like apollo-client has)?
  3. Do we provide a solution for basic authentication and OAuth 2.0 (my personal goal), and let other contributors fill in what they strategies need (OAuth 1.0a, API key, etc.)

What are your params and what do you want the auth solution to handle?

I'm going to fork the current state of this repo (apollo-fullstack-starter-kit) and we can use this as a starting point for creating a working solution.

GiladShoham commented 7 years ago

@swhamilton

Don't reinvent stuff, use other packages/work as much as necessary.

Agree

Don't create something that has multiple concerns. Break into separate packages if necessary

Not sure about different packages, but different files for sure. My extension is already split to about 5 different packages/node modules. So it's more about integration than about new packages.

Don't force lock-in to this solution (goes with 2)

We should make it easy to remove I think. Either by good docs and good code architecture.

Start small and keep the code as simple as possible. (should be Reasonaboutable, so others can contribute without knowing everything)

This is always good practice:)

Needs to have tests written

Same as before. But it's not very easy to write testing for auth integration.

your questions:

Do we use apollo-passport as a starting point even though it's technically deprecated, or do we take a step back and use only Passport (without apollo-passport) and create a new library/strategy to handle integration with this boilerplate?

It's not really deprecated. The original packages are, but the ones I forked and update are not deprecated. I actively update them. So yes, I can't see any reason to start from scratch. We can improve my packages along the way if needed. It's mine, so I know we won't get stuck without response from the author;)

Do we add a basic dummy DB (just a file with update/save/delete functions and in-memory arrays) for handling any type of token storage and let the community create DB drives (like apollo-client has)?

I created a mongo driver for my usage, and there is also rethinkdb driver which should work without any problem (didn't try it). It's already built in a way you can just put your own driver. I think we should start with the mongo which we know works well, and later think if we want to add more driver by us, or let the community write their own with good docs.

Do we provide a solution for basic authentication and OAuth 2.0 (my personal goal), and let other contributors fill in what they strategies need (OAuth 1.0a, API key, etc.)

My solution is based on Passport, it should work fine with any strategy passport support which is about 200. Personally, on my project, I used the local strategy with email and password. I think we should start there, and add few more out of the box (facebook, google, Github).

Regarding my concerns:

I worked very hard for a quite long time until I successfully make all the parts work together. I really want to save this time for the next developers, and we know everyone needs auth.

I want my solution to be used since I think it's a good solution which contains most of the required parts already, and I want to give it to the community so more people will use it and improve it.

We should also think about some UI for this, something which is good enough as a start point, but not too fancy, and maybe improve it later.

The main goal I think is to just have a full cycle of the auth: Register -> account activation by link sent to email -> login -> logout Recover password -> recover link by email Reset password

The auth should work on both sides, client, and server with the same object structure.

I think I can create a first dirty draft (not working), pretty quick, then we will start to make it really work. It will probably take a few days because we are just about releasing a new version for our product. So you can start ahead if you have time. I'll be very responsive (here or by email) to any question or help you need.

swhamilton commented 7 years ago

@GiladShoham This all looks great and I'm excited to get started digging in. Your goals are directly aligned with where I would like to contribute ;)

I think we can approach this in three phases:

  1. Implement apollo-passport into this starter kit. Just make it work and take shortcuts if necessary. This will also help us decide on architecture and possible opinions we have to make to provide a full solution.

2.a Identify the necessary parts for integration with this starter-kit and determine how to make apollo-passport generic enough to work outside this starter kit. 2.b Abstract the hard-coded parts into packages, providing appropriate APIs/options to give full customization. If the code is not able to be abstracted into a package, we will write several bare-bones auth examples with different implementations.

  1. Add the meteor-accounts registration flows, including email templates, password recovery: Register -> account activation by link sent to email -> login -> logout Recover password -> recover link by email Reset password

Let me know what you think about these phases and if you have any suggestions.

I think I can create a first dirty draft (not working), pretty quick, then we will start to make it really work.

Can you post/fork an example apollo app using your modified apollo-passport packages? This will be our starting point.

Would like to get a Slack/Gitter channel started to communicate and track progress. You can also contact me at scott@roadchow.com for further discussions.

larixer commented 7 years ago

@swhamilton Hey guys, I have brought up gitter chat for this project here: https://gitter.im/sysgears/apollo-fullstack-starter-kit

It is linked now from project README

mairh commented 7 years ago

@swhamilton @GiladShoham Are we still in discussion mode with the authentication and authorization feature?

ramanacv commented 7 years ago

Even I am curious and interested. Ready to start a new project. Have identified this starter kit as a boilerplate. Will be good if authentication/authorization is also part of it.

swhamilton commented 7 years ago

@mairh @ramanacv I have successfully implemented JWT authentication in this project with a react-native client (using AsyncStorage), and will soon be trying out the web side with a JWT in cookies (I want to preserve SSR). Once I complete the integration I will have a better understanding of how my approach works and I'll share my findings/post code snippets in a readme, etc.

A couple concepts I've discovered thus far with the JWT approach:

There are other considerations like using DataLoader that I need to explore more too.

mairh commented 7 years ago

@swhamilton So after validating through JWT we pass the user into graphql context? Until the user is authorized we don't hit the graphql backend, right?

Also, all the necessary info will be saved into graphql context and we can just do the normal graphql queries/mutation to do stuff with the authorized user?

swhamilton commented 7 years ago

@mairh

So after validating through JWT we pass the user into graphql context? Until the user is authorized we don't hit the graphql backend, right?

Because I use graphql in my authentication flow (mutations: signUp, signIn, authenticateToken), I can't block unauthorized users from hitting the API. I also have public resources that I want to serve, so I can't assume an invalid/no token request is bad. So for protected resources, I do authentication checks (are they a valid user) inside the resolver, and authorization (do they have permission) inside the controller (the controller is what actually grabs the data from the DB, so in my case I'm using mongoose.

Also, all the necessary info will be saved into graphql context and we can just do the normal graphql queries/mutation to do stuff with the authorized user?

For security and performance reasons you don't want to put the entire user object into the JWT. You can already tell if a user is authenticated by if the token is valid or not. Then inside the token you store the roles/permissions you need to check authorization. For me, I simply store the user roles and email inside the token, but the user object in the DB is larger.

When you need other parts of the user, you simply have to make an extra request in your resolver or controller to get the full user object once you determine that the token is valid.

In my react-native app, I store the JWT on the client, then when the app loads, I do an "authenticateToken" graphql mutation which checks the token validity (ie. not tampered or expired). If token is valid, then i grab the full user object and pass it back to the client, which then stores it in the redux store for future use.

mairh commented 7 years ago

That sounds logical to me. Would be really interested to see it integrated into this starter kit because doing authentical and authorization in graphql projects is not something very standardized.

Since now we also support react native into this starter kit I think having an auth module with react native client and web client support would be awesome. Let us know if in any way we can help you to get a draft PR done soon.

ramanacv commented 7 years ago

@swhamilton Thanks for the update provided. Our's is a web project. So I will look forward to your web integration. Thanks for stating your observations.

@GiladShoham I am meanwhile looking at this project. You mention this is not ready. Is it not feature ready or is it testing and security-audit that makes it incomplete ? Do you know when it will be ready ?

sakhmedbayev commented 7 years ago

@swhamilton and @GiladShoham, thank you for all the hard work you are both doing to integrate Auth and authorization into this kit. Is there any news where are you standing regarding this issue?

swhamilton commented 7 years ago

@sakhmedbayev Sorry for the late reply. I have been "field testing" my current auth solution with react-native and seeing what works best.

My current setup:

I load the token on each incoming request using the apollo networkInterface middleware and extract the token details on the server in the context (both http and websocket context, although the new Apollo seems to expect all traffic to go through websockets...need to look into that)

Here's some demo code:

// middleware/graphql.js
export default graphqlExpress((req, res) => {

  const token = getTokenFromRequest(req);
  const tokenData = verifyToken(token);

  try {
    return {
      schema,
      context: {
        ...modules.createContext(),
        token,
        tokenData,
        auth: {}, //TODO: add helper methods here ?
      }
    };
  } catch (e) {
    log(e.stack);
  }
});

I haven't tested the web-cookie JWT approach where the initial request includes a cookie with the necessary auth info to properly use SSR with protected routes, but my initial PoC seemed to work. I will update more as I implement user auth on my website, not just react-native.

larixer commented 7 years ago

Nice idea: https://github.com/kkemple/graphql-auth

mairh commented 7 years ago

But still we have to develop the auth module before we plug in the middleware resolver :) I've been waiting for auth to be a part of this kit.

larixer commented 7 years ago

@mairh Yes, sure

sakhmedbayev commented 7 years ago

In a mean time, can someone please demonstrate (show a full example) how authentication and authorization can be implemented with this starter kit? I will greatly appreciate your help!

I found this solution https://github.com/StephenGrider/auth-graphql-starter by Stephen Grider, but it uses mongoose+mongodb. Perhaps someone can convert it to use with this starter kit?!

sakhmedbayev commented 7 years ago

@mitjade, in your example (below), what have you done to properly feed a result of this function call modules.createContext() into context?

export default apolloExpress((req) => {
  let serial = '00';
  if((req.headers.serial)) {
    serial = req.headers.serial;
  }

  return {
    schema,
    context: {
      Serial: serial,
      ...
    },
  };
mitjade commented 7 years ago

@sakhmedbayev This was done before modules.createContext() was introduced to this starter kit.

I still do not have a proper solution for this. Currently I bypass context all together and export user from graphql.js and then import them into resolvers that I need. My main problem was that with new subscriptions you need to pass context again onConnect in subscriptions.js. When I will have some time will try to find a proper solution for this and share it.

sakhmedbayev commented 7 years ago

Here is what I have done

in graphql middleware

export default graphqlExpress((req) => {
  try {
    return {
      schema,
      // context: modules.createContext()
      context: {
        modules: modules.createContext(),
        SECRET
      }
    };
  } catch (e) {
    log(e.stack);
  }
});

and then in each of a module's resolvers.js file, I changed context.ModuleName.SQLFunction() to context.modules.ModuleNmae.SQLFunction(). Something like this

  Mutation: {
    async addPost(obj, { input }, context) {
      let [ id ] = await context.modules.Post.addPost(input);
      let post = await context.modules.Post.getPost(id);
      // publish for post list
      pubsub.publish('postsUpdated', { mutation: 'CREATED', id, node: post });
      return post;
    }
}
veeramarni commented 7 years ago

What you guys think about this example using ooth

mitjade commented 7 years ago

@vlasenko I was able to integrate part of the https://github.com/kkemple/graphql-auth into one of my projects. I do not need to login since I use client side certificate, so this part is still to be made.

If this is something that would be meaningful for this starter kit I am willing to give it a try and also add create, and login user for web with LocalStorage and native with AsyncStorage. It would be a simple username and password and no passportjs, added as an example module like counter and post. Later post example could be used to show how to accomplish simple Authorisations.

If anyone else already has a solution, I would be really grateful to give me a hand making this work.

larixer commented 7 years ago

@mitjade I think it would be a great start for the authentication and authorization support in the starter kit, that we will be able to use for full auth support in the future.

sakhmedbayev commented 7 years ago

@mitjade, I was able to make authentication and authorization work with this kit using jwt token. At the moment do not have time to share it with community. Please look at the tutorials on this channel. There are all you need to make it work https://www.youtube.com/user/99baddawg

mitjade commented 7 years ago

I created a PR(https://github.com/sysgears/apollo-universal-starter-kit/pull/325).

I tried to follow the videos @sakhmedbayev recommend and managed to get some of it working.

Currently I am not able to add afterware, so I could set token on page reload. My queries get no data back if I navigate to a page where I run them. I'm also not sure how to correctly set this up with SSR.

@sakhmedbayev You said you ware able to get this working. Could you spare some time and take a look at my PR and help me out?

I still need to then integrate graphql-auth and make it work for mobile.

sakhmedbayev commented 7 years ago

Here is how I set afterware

networkInterface.useAfter([{
  applyBatchAfterware(res, next) {    
    const token = res.options.headers['x-token'];
    const refreshToken = res.options.headers['x-refresh-token'];
    if (token) {
      localStorage.setItem('token', token);
    }
    if (refreshToken) {
      localStorage.setItem('refreshToken', refreshToken);
    }
    next();
  }
}]);
mitjade commented 7 years ago

@sakhmedbayev Found the problem. It was the way I used res in afterware.

Thank you for your quick reply.

sakhmedbayev commented 7 years ago

@mitjade, I am not sure if I set SSR correctly in my solution. Do you need that pages that require auth to be SSR? Why?

mitjade commented 7 years ago

@sakhmedbayev What if you already logged in before, so you have a valid token stored and then go to an auth page directly (reload page)?

sakhmedbayev commented 7 years ago

Sorry, cannot tell anything about integration with graphql-auth, currently building web app.

mitjade commented 7 years ago

I have graphql-auth working at one of my projects. So that I know how to setup.

sakhmedbayev commented 7 years ago

@mitjade, You are right my setup cannot handle direct landing to pages that require auth :-(

mitjade commented 7 years ago

@sakhmedbayev How did you setup subscriptions?

sakhmedbayev commented 7 years ago

@mitjade, I was probably to quick to tell that I made auth working with this kit. Currently, my web app does not require any subscriptions.

mitjade commented 7 years ago

@sakhmedbayev No problem. Any help on this matter is helpful. Will try to figure out that part on my own.

@vlasenko Could you maybe help out with the SSR and auth problem. I do not quite understand how this should work. Should direct access to a auth page be only allowed on the client and we should somehow prevent SSR for those pages?