Closed sakhmedbayev closed 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?
@mitjade Yes, we could. But this topic is very wide. What authentication scheme we should support and using which implementation?
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.
Maybe to include basic local authentication with email and OAuth 2 with github???
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.
@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.
I think authentication and authorization fall under the category of "bare minimum", at least in my opinion :)
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.
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.
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?
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.
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:
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
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!
@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!
@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:
Some questions:
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.
@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).
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.
@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:
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.
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.
@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
@swhamilton @GiladShoham Are we still in discussion mode with the authentication and authorization feature?
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.
@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.
@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?
@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.
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.
@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 ?
@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?
@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.
Nice idea: https://github.com/kkemple/graphql-auth
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.
@mairh Yes, sure
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?!
@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,
...
},
};
@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.
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;
}
}
@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.
@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.
@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
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.
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();
}
}]);
@sakhmedbayev Found the problem. It was the way I used res
in afterware.
Thank you for your quick reply.
@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?
@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)?
Sorry, cannot tell anything about integration with graphql-auth
, currently building web app.
I have graphql-auth
working at one of my projects. So that I know how to setup.
@mitjade, You are right my setup cannot handle direct landing to pages that require auth :-(
@sakhmedbayev How did you setup subscriptions?
@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.
@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?
Thank you guys for this starter kit. It is awesome!!! Would be nice to have authentication and authorization support. What do you think?