-
-
Notifications
You must be signed in to change notification settings - Fork 570
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
get JWT from cookies and customize response headers from stored procedure #501
Comments
Passing JWT via the |
If the standard encourages localstorage over HttpOnly cookies, that's silly. Cookies are simply more secure. See here for example: https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage Thank you for the example of how to pack my own JWTs |
I don't think it recommends any storage medium, only a transport medium (the You're right that I would not recommend storing the JWT to localStorage, and especially not to client-side cookies; but HttpOnly cookies is a definite option. Combined with So if I understand your intention correctly, all we'd need to do is when a JWT is generated, in addition to sending it how we currently do (in the payload), also set it in an HttpOnly cookie. Similarly when looking for a JWT we'd look first in the Authorization header, falling back to the named cookie. Or maybe we'd check both and throw an error if they're both set but to different values? Does this sound in line with what you're suggesting? |
Yes, HttpOnly and Secure -- will make it really hard to steal the JWTs |
I really like the idea of checking both, because that builds in a degree of CSRF protection, also. Ideally, though, the double-check would be on a separate token, like double-send cookie nonces so they can be generated on a front-end server separately from the API, rather than requested from the API like the JWT is. Also, this prevents the need for the API to ever send the JWT to the client, so no amount of XSS would be able to steal the session. |
Cookie nonces are a separate ticket, but it could be argued that implementing cookie-based auth would be less secure without some form of CSRF protection. |
@benjie How could I build this as a plugin for PostGraph4? |
The plugin system of PostGraphQL4 currently only deals with the GraphQL schema generation... extending it to cover the HTTP server too is an interesting idea. For now; we'll probably just carry over the HTTP handling from PGQL3 so implementing it on master would work. I wasn't suggesting that we'd require both to be present (unlike a double-submit cookie), I think if we send the token through both mechanisms then if you're a browser you'll automatically use the cookie (no other code required), but if you're a non-browser client (like a native or command line application) then you can use the traditional |
This is why I was thinking to just set custom headers from stored procedures, somehow. I could optionally make a non-cookie-based auth endpoint for the non-browser client with a JWT and then also make a cookie-only one for the browser where I'm worried about XSS. It'd honestly be cool to have access to the headers going into some of the stored procedures as well. Exposing a |
Could you demonstrate a tiny proof of concept? I can't quite see how it'd work, since it's kind of bypassing the GraphQL<->PostgreSQL two way thing. (I'm not familiar with the JWT generation features of PostGraphQL yet - I've not used them.) |
yeah the "somehow" for the custom headers is a trick. Basic cookie auth with a flag is much easier. I'll think about a PoC for the custom headers from the stored procedure. You probably don't need a PoC for basic cookie auth ;) |
Here's a talk by an AppSec professional about, among other things, double-submit cookies: https://youtu.be/cSOKJRfkTDc?t=21m4s |
@benjie I'm going to want to add this in before my September 15th launch -- can you point me to parts of graphile / postgraphile that facilitate HTTP Header exchange, and points of Graphile / postgraphile where a field returned from a stored procedure might be manipulated and handed off to the HTTP header exchange code? |
I'm still not sure off-hand the best way to do this; but here's a couple pointers that may help you get started. JWT -> PostgresThe jwtToken is extracted from the request here: That's then passed to It should be quite easy to extend the above to pass arbitrary headers through to the DB. (If you choose to do so I would like the specific headers explicitly listed (in the same way Postgres -> JWTThis is more challenging because we don't actually DO anything with the JWT, we just generate it and let the client interpret it as part of the JSON payload from Basically it says "whenever you see the composite type the settings have specified is the JWT type, instead of processing it as a composite type convert it as to a scalar string by signing it as a JWT token using the specified secret". This isn't even directly related to a resolver (the resolver calls pg2gql which calls the above code), so without a bit of wrangling we can't even call a callback that's passed through on context or something like that :( AlternativeCould we (ab)use What are your thoughts? |
( |
For Postgres -> JWT I want to be able to return either a custom type or a custom field name (something like __HEADER__Authorization) from a stored procedure and have it just be a header. With a custom type, I've had some issues nesting custom types, so a type like Headers( header, header, ... ) being returned from a stored procedure would be ideal but may be challenging for currently unknown reasons. amazing writeup above... thank you so much @benjie |
Oh, man, no you know what... The best solution would be to just use What do you think? :) |
So, beyond just setting custom headers, the question of how do we get the JWT value to set the header is still outstanding... Trying to better grok https://github.com/graphile/graphile-build/blob/7ecec2c3a851c5ef683f87332ac163c8690c1625/packages/graphile-build-pg/src/plugins/PgJWTPlugin.js#L73-L115 Basically, we're making a GraphQL type for the JWT currently? With a Ultimately, there will be origin restrictions as well, but this is more than enough of a good start. Getting the token out of the front-end JavaScript to subvert possible XSS session hijacking attacks is what this is all about. |
Looks like I can get to the req object if I register a middleware: https://github.com/postgraphql/postgraphql/blob/2c80a1801ae555681412aebb74993e6df930c0b1/src/postgraphql/http/createPostGraphQLHttpRequestHandler.js#L306 |
I think the problem here is that the HttpRequestHandler is tightly coupled with https://github.com/graphile/graphile-build/blob/7ecec2c3a851c5ef683f87332ac163c8690c1625/packages/graphile-build-pg/src/plugins/PgJWTPlugin.js#L73-L115 Or, rather, vice versa? Ultimately, the PgJWTPlugin should be doing its own JWT extraction, validation, and response forming. Is this more a conversation about plugin architechture and/or additional necessary hooks? |
Really the issue is that the parts are independent rather than coupled: HttpRequestHandler parses the incoming JWT and sets those values on the PG connection - i.e. the GraphQL schema itself knows nothing of them (but they do influence the results it gets, because PG has been told about them). PgJWTPlugin looks for a particular type being returned from Postgres and intercepts it, instead converting it to a JWT string which it then returns via GraphQL; HttpRequestHandler knows nothing of this currently (it just passes through the JSON blob returned by GraphQL). A potential solution arises...With So we can wrap those resolvers with something like this: const oldResolver = field.resolve;
field.resolve = async function(data, args, context, resolveInfo) {
const jwtString = await oldResolver.call(this, data, args, context, resolveInfo);
if (jwtString != null && typeof context.pgHandleJWT === 'function') {
context.pgHandleJWT(jwtString);
}
return jwtString;
} Then in Which can be passed through from HttpRequestHandler: And thus can do what it likes including HOWEVER, the issue with this is that the user HAS to request the jwtToken on the response because if they don't then the resolver won't fire and thus the cookie will not be set. So it's not perfect... I'm still thinking the |
I say that they're coupled because, ideally, there'd be a JWT plugin which gets the req and pulls out the JWT -- the HTTPRequestHandler has an extra responsibility. I need to better understand what you've said above, might take me a couple read-overs. Thank you again for your time :) |
Yeah, it takes quite a bit to grok exactly how it works and why it works like that - have to have quite a fundamental understanding of how GraphQL resolvers work. |
I'm pretty sure that |
Make sense that RAISE INFO is synchronous. Haven't given this ticket the
time it deserves yet. Still need to grok the plugin arch. I have a couple
goals in mind, but nothing worth talking about until I do some work on this
-- otherwise, we will be rehashing the same bits.
On Thu, Aug 17, 2017 at 4:06 AM Benjie Gillam ***@***.***> wrote:
I'm pretty sure that RAISE INFO is synchronous FWIW - like error handling
it would have to be?
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#501 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAmxIn60E0BLYpLwXgYoPe62YaMMJl8xks5sY_RrgaJpZM4OQ8wT>
.
--
Senior Web Applications Developer
https://chads.website
https://github.com/chadfurman
https://linkedin.com/in/chadfurman
|
Rather than adding we'd add a new EventEmitter instance ( |
@benjie "Maybe it's passed in from the library settings" -- can you elaborate? "Passed down via the GraphQL context" -- could you show an example of pulling this into a mock plugin quick? "you could use this for example to do postgraphqlEventListener.emit('set-cookie', 'foo=bar') which could be interpreted by your application to set a cookie." -- When you say "interpreted by your application to set a cookie", where in the Postgraphile app would I have access to the request object to be able to set cookies, and how could I subscribe to the event at that point? |
I mean something like this: export postgraphql(process.env.DATABASE_URL, schemaName, {
eventListenerFactory: (req, res) => {
const emitter = new EventEmitter();
emitter.on('set-cookie', (cookie) => { res.setCookie(...) });
//...
return emitter;
}
})
This would not be possible via a plugin as the context exists outside of the GraphQL schema. It would be a mod to withPostGraphQLContext: But once that mod is in place, you could then fire it from an arbitrary resolve method: module.exports = function CreateLinkWrapPlugin(builder) {
builder.hook(
"GraphQLObjectType:fields:field",
(field, _, { scope: { isRootMutation, fieldName } }) => {
if (!isRootMutation || fieldName !== "someMutationToMonitor") return field;
const defaultResolver = obj => obj[fieldName];
const { resolve: oldResolve = defaultResolver, ...rest } = field;
return {
...rest,
async resolve(resolveParams) {
const RESOLVE_CONTEXT_INDEX = 2;
const { pgClient, postgraphqlEventListener } = resolveParams[RESOLVE_CONTEXT_INDEX];
const callback = something => postgraphqlEventListener.emit('something', ...);
pgClient.on('whatever', callback);
try {
return await oldResolve(...resolveParams);
} finally {
pgClient.removeListener('whatever', callback);
}
}
};
}
)
};
Hopefully the first example makes this clearer |
Yes! Thank you @benjie 👯♂️ |
We should be collecting things like this on the wiki I guess... |
It’s been a while since the original topic “get JWT from cookies and customize response headers from stored procedure“ was raised. Is this feature now supported? |
@benjie @chadfurman interested in this too so, what was the result of this issue? is there exists plugin / is it possible to enable retrieving jwt from a cookie also
|
See pgSettings function referenced above. |
Use pgSettings, the jsonwebtoken module, and a cookie parser in your server stack (e.g. express’ cookie parser). |
@benjie thank you. You could give me an example, I'll attach code. I'm working with Next Js. // postgraphile.ts
// runMiddleware.ts
|
const { postgraphile } = require('postgraphile');
const express = require('express');
const cookieParser = require('cookie-parser');
const jwt = require('jsonwebtoken');
const app = express();
app.use(cookieParser());
app.use(postgraphile(DB, SCHEMA, {
...options,
pgSettings(req) {
const { myJwt } = req.cookies;
const claims = jwt.verify(myJwt, process.env.JWT_SECRET);
return {
'jwt.claims.user_id': claims.user_id,
};
}
}));
app.listen(5000); |
@benjie i'm not working with express js, only next js. do I have to add express js necessarily for this? Ps. I'd like to memorize JWT in cookies, but only HTTP |
I cannot advise you on how to do it with other frameworks; you’ll have to figure that part out yourself. |
Can I pass JWT Token in cookie rather than passing it to Header |
Use the |
Sir I have one doubt like I am generating JWT token through Spring Boot and it is generating fine with my username and password . But I want to ask that is it possible to convert that token into Cookie or pass that token in cookies because in postman Cookies option is also disable thats what I am asking that how can I implement this please sir help me? |
Sorry, I am not familiar with postman. Try using the bundled PostGraphiQL; use the |
Cookie first, fail back to header, with default role when no token found (in case pgDefaultRole can not be set when pgSettings enabled) const pgSettings = async (req) => {
const token = req.cookies.jwttoken || req.get("Authorization")?.split(" ")[1];
if (token) {
const claims = jwt.verify(token, process.env.JWT_SECRET);
return {
"jwt.claims.user_id": claims.user_id,
role: claims.role,
};
} else {
return {
role: "forum_example_anonymous", // please change it to your default role
};
}
};
|
Cookies have the ability to be HttpOnly and can require HTTPS -- things that local storage cannot do. If the client must know the JWT to send the request, that means the JWT is accessible via JavaScript. Postgraph should be able to pull the jwt out of a cookie with a name of your choosing, rather than relying on the Authorization header.
To get the JWT from a cookie we'd need to change getJwt:
https://github.com/postgraphql/postgraphql/blob/607f6629986735139e2b77d5a9b1143846c4f691/src/postgraphql/http/createPostGraphQLHttpRequestHandler.js#L548
Getting the JWT into an HttpOnly cookie is another can of worms. Maybe we could have it so if we return an optional, configurable field (i.e. select 'Cookie: ........' as responseHeaders) then the response header is automatically set and in this way we can support all sorts of response-header types including mime-types for dumping binary data straight out of the database.
The text was updated successfully, but these errors were encountered: