-
Notifications
You must be signed in to change notification settings - Fork 53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Isomorphic JWT authentication #15
Comments
This is almost exactly the architecture I am using. I pass the token as an The problem I have is passing different tokens through to the relay I think there are some pull requests to Facebook to add contexts so a On Tue, 9 Feb 2016, 06:28 Fabrizio notifications@github.com wrote:
CTO - Enabled |
@DylanSale Thanks a lots for your feedback, I thought that isomorphic-relay would give also the context for differentiate caches, i will find out more about thanks. About your comment saying:
How do you access the token from the client side if is a http only cookie i missed that part. I find my self forced to render the token into the DOM |
You do not want to access the cookie from the client side generally. You only retrieve it on the server and give it to relay, for instance as a root value, like here: // Graphql server
router.use( '/graphql', graphQLHTTP( request => {
let user_id = '00000000-0000-0000-0000-000000000000'; // Anonymous
try
{
if( request.cookies.auth_token )
if( request.cookies.auth_token.length > 10 )
{
var decoded = jwt.decode( request.cookies.auth_token, process.env.JWT_SECRET );
user_id = decoded.user_id;
}
}
catch( err )
{
console.log( chalk.bold.red( "Failure while decoding JWT token, using anonymous instead." ) );
console.log( chalk.red( err.message ) );
console.log( chalk.blue( '.' ) );
}
return( {
schema: schema,
rootValue: { user_id: user_id },
pretty: true
} )
} ) ); and then for server rendering you take it from the request and inject it into the network layer (global object!!): // Setting the STATIC network layer. No fear about it being static - we are in a queue!
Relay.injectNetworkLayer( new Relay.DefaultNetworkLayer( GRAPHQL_URL, { headers: headers } ) );
RelayStoreData.getDefaultInstance( ).getChangeEmitter( ).injectBatchingStrategy(() => { } ); (shameless self promotion ahead) |
@thelordoftheboards I get it! Thanks! Your repo is also very interesting! 👍 I will go trough it. I will do some trials tomorrow |
@thelordoftheboards I did some trials tonight trying to figure out what you suggested yesterday, and I came to the conclusion that i'm still miss something. Your repository is great, but the difference between my architecture and yours is that my GraphQL server is not aware about sessions. Just the isomorphic app, and the requests are not sent to the Isomorphic app which has capability of handling sessions, they are directly sent to the GraphQL server which would want the JWT. This is what i tried to do: renderOnServer middleware const TokenFromSession = (req.session && req.session.token) ? req.session.token : null;
// Verified token to graphQL server
Relay.injectNetworkLayer(new Relay.DefaultNetworkLayer(GRAPHQL_URL, {
headers: {
Authorization: 'Bearer ' + TokenFromSession
}
}));
RelayStoreData.getDefaultInstance().getChangeEmitter().injectBatchingStrategy(() => {});
IsomorphicRouter.prepareData(renderProps).then(render, next); now on the client i need to give the token again to Relay on the Default Network otherwise it wouldn't pass it to sub-sequential requests. client.js const GRAPHQL_URL = `http://localhost:8085/graphql`;
// HERE i need to give the JWT back to relay so it can send it every request! How??!
Relay.injectNetworkLayer(new Relay.DefaultNetworkLayer(GRAPHQL_URL));
let data = [];
// Preload relay data
if (document.getElementById('preloadedData').textContent) {
data = JSON.parse(document.getElementById('preloadedData').textContent);
}
IsomorphicRelay.injectPreparedData(data);
const rootElement = document.getElementById('content');
ReactDOM.render(
<IsomorphicRouter.Router routes={routes} history={browserHistory} />,
rootElement
); I'm spending a lot time to find the "right" way to do it. At the moment i'm hacking it, to let me continue with other functionalities. Any suggestions would be appreciated |
How about this change, to allow it to transfer the cookie? Relay.injectNetworkLayer( new Relay.DefaultNetworkLayer( GRAPHQL_URL, { credentials: 'same-origin' } ) ); |
I thought about it, but even if I allow Relay to pass the cookie to the GraphQL i don't think it would work: Isomorphic app, would issue the HttpOnly cookie. Edit: Now that i'm thinking to let the GraphQL receive the cookie and get the information from the session would break the logic of having GraphQL servers stateless |
To be honest I can not say much about this. I have only survival skills with cookies and don't know the intricacies of how they work cross domain. One way to find out I guess ... |
@thelordoftheboards eheh same here about cookies, I'm just drawing diagrams over and over to find the correct flow. The only solution that i recently thought for achieve this is to store the token into the local storage of the browser, as well as in the httpOnly cookie so that on refresh i'm going to verify the token from the session If the backend find out that the token is invalid, i can just render into the DOM an object containing a flag of validity: <script id="validity" type="application/json">
{"isValidToken": false}
</script> so that on the client, i can remove the token from the local storage. Here i'm illustrating a basic example, of course there would be many scenario to cover, but local storage is better then having the token into the DOM on the first render client.js const validity = JSON.parse(document.getElementById('validity').textContent);
const headers = {};
if ( validity.isValidToken) {
headers.Authorization = 'Bearer' + localStorage.getItem("token");
} else {
localStorage.removeItem("token");
}
const GRAPHQL_URL = `http://localhost:8085/graphql`;
Relay.injectNetworkLayer(new Relay.DefaultNetworkLayer(GRAPHQL_URL,headers));
let data = [];
// Preload relay data
if (document.getElementById('preloadedData').textContent) {
data = JSON.parse(document.getElementById('preloadedData').textContent);
}
IsomorphicRelay.injectPreparedData(data);
const rootElement = document.getElementById('content');
ReactDOM.render(
<IsomorphicRouter.Router routes={routes} history={browserHistory} />,
rootElement
); |
I like your solution. However, if you decide to store in local storage, please consider the security implications, not sure which ones apply to your case: https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage/ |
@thelordoftheboards thanks to point me about security of storing the token into the local storage. I read a lot about it and seem how you said that wouldn't be the best thing to do after all. The hacks that i can see with this approach would be only the self-xss which definitely i want to prevent. Said that i have another solution and i think will be my final implementation, i would do as following. On the client.js instead to get token from the local storage I would delay the front-end initialisation to one more request ("which should resolve pretty quickly"). The request would be sent to the isomorphic app for ex: 'http://localhost:8000/me" the entry point would just check your session and return the token to you, (if you are logged in) /me entry point on isomorphic app app.post('/me', (req,res) => {
// CRSF token validation passed
if (req.session && req.session.token) {
return res.json({token: req.session.token});
}
return res.json({authenticated: false});
}); So on the client.js this would happen: const fetchTokenOptions = {
method: 'POST',
credentials: 'same-origin'
};
fetch('http://localhost:8000/me',fetchTokenOptions).then((response) => {
response.json().then((jsonRes) => {
const headers = {};
const GRAPHQL_URL = `http://localhost:8085/graphql`;
// Only if the token is returned from the isomorphic app
// would be given to Relay
if (jsonRes.token) {
headers.Authorization = 'Bearer ' + jsonRes.token;
}
Relay.injectNetworkLayer(new Relay.DefaultNetworkLayer(GRAPHQL_URL,headers));
let data = [];
// Preload relay data
if (document.getElementById('preloadedData').textContent) {
data = JSON.parse(document.getElementById('preloadedData').textContent);
}
IsomorphicRelay.injectPreparedData(data);
const rootElement = document.getElementById('content');
ReactDOM.render(
<IsomorphicRouter.Router routes={routes} history={browserHistory} />,
rootElement
);
});
}); What do you think about this approach? (it would need the CRSF token protection on this entry point to make it more secure) I believe it cover all security problems and the request would be relatively resolved pretty fast, it would need just to check the session storage (redis). |
+1 |
@thelordoftheboards Could you please explain what the code in the second line do?
|
@tuananhtd My understanding is that when we inject the network layer, we also pass the headers, which include some kind of user-specific token which is not displayed in your example. We will presumably derive a root value based on this token. @fenos sorry for the delayed reply, maybe you have already figured out a satisfactory solution. Have you considered the relay local schema? |
@thelordoftheboards I know but what make me curious is the second line. Does it propagate the change of new injected network layer to the Relay Store? |
@tuananhtd this is above my competency. I am curious about the answer too. @denvned ? |
Since v0.6 of isomorphic-relay, probably the most simple approach is to create a new instance of a Relay network layer (with injected user session data/JWT headers/cookies) on each request and pass it as the second argument to |
Hi @denvned, I'm focusing my project on this package which provide isomorphism to relay.
This is not a question related to the package "Which works great" but more related to the isomorphism world and authentication. I'm writing here because i believe you may suggest something very precious to me and community regarding this subject.
In my current project I want to achieve the following criteria with an isomorphic app:
To solve this I needed 2 authentications, one for the isomorphic app "session based" and the second for the GraphQL server which is the wanted JWT.
As following I created a diagram that describe this authentication flow:
note: it start from the browser requesting authentication token to graphQL
The biggest downside of this approach "The reason why i'm posting here" is the way how I pass the token to Javascript / React. I'm actually rendering the token in a div Similar way how we preload the data with relay "It render the data as a JSON string in a HTML element"
On DOM loaded i extract the token information from the
<div id="auth">{"token":"usertoken"}</div>
and pass it to a
AuthStore.js
which keep it in memory and pass it to Relay for sub-sequential request.I implemented this approach and it works just fine, but i'm concerned about security of rendering the token in the div. If you have any better idea to pass this token to Javascript would be great. Or did you ever done a similar authentication with a Isomorphic app?
Thanks for the time to read this
The text was updated successfully, but these errors were encountered: