grand-stack / graphql-auth-directives Goto Github PK
View Code? Open in Web Editor NEWAdd authorization to your GraphQL API using schema directives.
Home Page: https://www.npmjs.com/package/graphql-auth-directives
License: Other
Add authorization to your GraphQL API using schema directives.
Home Page: https://www.npmjs.com/package/graphql-auth-directives
License: Other
Looking at the code published in 2.2.0, I noticed a few issues.
The previous header retrieval logic took advantage of short-circuiting to avoid issues of attempting to access properties of an undefined
object:
!req ||
!req.headers ||
(!req.headers.authorization && !req.headers.Authorization)
At each stage of this predicate, the ||
ensures we're not attempting to access fields of an undefined
object.
But as of 2.2.0:
(!req ||
!req.headers ||
(!req.headers.authorization && !req.headers.Authorization)) &&
(!req.cookies && !req.cookies.token))
There are actually 2 bugs in just these 4 lines of code, whereby we may attempt to access the fields of an intermediate undefined
object.
First, let's say that the initial !req
evaluates to true
. Because the initial logic is now adjoined with (!req.cookies && !req.cookies.token))
we will now attempt to evaluate !req.cookies
, but req
is undefined
, and we blow up.
Second, the same logic applies within (!req.cookies && !req.cookies.token)
itself, if req.cookies
is undefined
: !req.cookies
evaluates to true
, so we attempt to execute !req.cookies.token
, and again blow up trying to access a property on an undefined object.
Because these are inside an if
statement and not a try
/catch
, these cases will propagate to the GraphQL error response, which I assume is not the intent.
In the first case, one might argue that a missing req
is violating the package's API, and thus no guarantees are provided / blowing up is fine. I'd tend to disagree, favoring the friendlier developer ergonomics of catching such cases and providing a useful error message.
In the second case, it's perfectly reasonable to not have a req.cookies
object. In fact this is the default in express
, absent a cookie-parsing middleware, which is how I ran into this issue when trying to upgrade.
Cross-post and extension of this issue, as it may be more readily addressed here.
At present, graphql-auth-directives
usage effectively co-opts the entire context object via returning only one sub-field of the context object it processes:
const server = new ApolloServer({
schema,
context: ({ req }) => { /* Here we pluck the variable `req` from context, via a destructured fn param */
return req; /* Here we substitute req for the entire context object from which it came */
}
});
If we inspect graphql-auth-directives
' verifyAndDecodeToken
function, we find that it takes for granted that the context
object is the req
object, which would not be the case if one were to configure the context
object with both driver
and req
, as is necessary when instantiating ApolloServer
.
Presently, if one instantiates ApolloServer
like so:
const driver = neo4j.driver(
"bolt://localhost:7687",
neo4j.auth.basic("neo4j", "letmein")
);
const server = new ApolloServer({
schema,
context: ({ req }) => {
return {req, driver};
}
});
Then the logic within graphql-auth-directives
will no longer find the auth header:
var verifyAndDecodeToken = function verifyAndDecodeToken(_ref) {
var context = _ref.context; /* <-- Note: I *will not* find the headers on "_ref.context.req" */
if (
!context ||
!context.headers ||
(!context.headers.authorization && !context.headers.Authorization)
) {
throw new _errors.AuthorizationError({
message: "No authorization token."
});
}
Perhaps something like (in ES6, rather than the transpiled output above):
const verifyAndDecodeToken = function verifyAndDecodeToken({context}) {
const req = context instanceof http.IncomingMessage ? context : (context.req || context.request);
...
Which would maintain backwards compatibility while allowing for context objects that possess req
as a property.
That said, this still has a bit of a code smell, as in the present code what's being called context
isn't necessarily the context
object in question: it's either the context or a req
of type http.IncomingMessage
.
Whereas the above suggestion should fix the issue in a backwards-compatible manner, it also unfortunately turns the local context
variable into a polymorphic type whose name doesn't closely match its underlying reality (e.g. it's really contextOrReq
).
Ideally the API for verifyAndDecodeToken
would take the full context as its only argument, then look for a http.IncomingMessage
at either of context.req
or context.request
, afterwards using the proper req
semantics in reference to the object in question (req.headers
, etc...). In this way the API of this library would make as few assumptions as possible concerning the structure of the context object passed to the ApolloServer
constructor, and minimize semantic confusion.
server:
import { IsAuthenticatedDirective } from "graphql-auth-directives";
import { makeAugmentedSchema } from "neo4j-graphql-js";
import neo4j from "neo4j-driver";
import { typeDefs } from "./graphql-schema";
const schema = makeAugmentedSchema({
typeDefs,
schemaDirectives: {
isAuthenticated: IsAuthenticatedDirective,
//hasRole: HasRoleDirective,
//hasScope: HasScopeDirective,
},
});
const server = new ApolloServer({
...
schema,
})
in my client, apollo.config.js
module.exports = {
client: {
service: {
name: "project",
// This option uses the resulting schema from makeAugmentedSchema
url: "http://localhost:4001/graphql",
skipSSLValidation: true,
},
}
When I run from my client npx apollo client:download-schema schema.graphql
, I cannot find the isAuthenticated directive. Everything else, like neo4j directives, is shown properly.
directive @cypher(statement: String) on FIELD_DEFINITION
directive @relation(name: String, direction: _RelationDirections, from: String, to: String) on FIELD_DEFINITION | OBJECT
directive @additionalLabels(labels: [String]) on OBJECT
directive @MutationMeta(relationship: String, from: String, to: String) on FIELD_DEFINITION
directive @neo4j_ignore on FIELD_DEFINITION
...
It looks like a bug with graphql-auth-directives, but since no other user reported it, I suppose it's my setup that's incorrect, though I cannot figure this out on my own. Any idea?
I would like to use this library, especially is it is included in neo4j-graphql-js by default, but we use a federated architecture with one gateway handling the authentication and then making subqueries to the individual services. So while it would still be nice to let the individual services decide which fields need auth, it's unnecessary to have each service validate the JWT, since they're only accessible through the gateway. What I'm saying is, it'd be nice to be able to turn JWTs off and just add the required context variables to the request as plain objects, and trust the that the gateway has already verified the authenticity of the claims.
GraphQL v16 has been released for a while, need to update peerDeps to reflect the compatibility.
graphql-auth-directives/package.json
Lines 57 to 59 in c1659a4
Add types for all neo4j-graphql.js exports to https://github.com/DefinitelyTyped/DefinitelyTyped
Or rewrite the project using typescript.
Same request as neo4j-graphql/neo4j-graphql-js#275, these are the only 2 dependencies missing types in my project, and make it difficult to resolve issues like #24
Error: token invalid
Hello!
Given I followed all the documentation regarding authentication on your site and added all the prerequisites for using the isAuthenticated
directive, when an authenticated request (it happens ONLY when having access token in headers) involving a type or field annotated with @isAuthenticated
is made, the following error comes back.
In order to fix it, I added the check for the next()
existence in the src/index.js
isAuthenticatedDirective
class of your library (closer to the end of it):
export class IsAuthenticatedDirective extends SchemaDirectiveVisitor {
static getDirectiveDeclaration(directiveName, schema) {
return new GraphQLDirective({
name: "isAuthenticated",
locations: [DirectiveLocation.FIELD_DEFINITION, DirectiveLocation.OBJECT]
});
}
visitObject(obj) {
const fields = obj.getFields();
Object.keys(fields).forEach(fieldName => {
const field = fields[fieldName];
const next = field.resolve;
field.resolve = function (result, args, context, info) {
verifyAndDecodeToken({context}); // will throw error if not valid signed jwt
if (next) {
return next(result, args, context, info);
}
return result[field.name];
};
});
}
}
Please fix it in the next possible release.
FYI - using the latest version of your library in the scope of neo4j-graphql-js
as of today:
Hello again,
When using @hasScope directive, an error occurs when annotated by the directive fields are being resolved and the scope is being checked against expected. The bug lies on line node_modules/graphql-auth-directives/dist/index.js:95
in the HasScopeDirective
class where the expectedScopes
point to the this.args.roles
instead of the this.args.scopes
:
Please fix it in the next release. FYI: I am using the latest version of your package as of today:
SchemaDirectiveVisitor
has been removed from @graphql-tools/utils
with the 8.0.0 release: ardatan/graphql-tools#3081
The new approach is to use mapSchema
and getDirectives
functions.
I am using a library which creates a jwt in the shape of { user: { roles: ... } }
Request / suggestion, to support this format.
Thank you.
It's currently not very clear which license applies to this package. While the package.json declares the license as Apache 2.0, the license text is not included anywhere in the repository, and there are no other mentions of any license. A LICENSE.txt would be nice.
Hi everyone,
How can I get the user info once I'm authenticated? I expected that it would be inside of context but seems that it is not in anyplace.
I've checked the source code and I see that decoded data is not attached to any place. Is there another way to get it?
Thanks
Is it possible to use the auth directives on subscriptions over websocket?
See this for the Apollo docs on subscriptions
https://www.apollographql.com/docs/apollo-server/features/subscriptions
Custom directives run after the data has been injected into the database and therefore unfit for the most obvious use-cases. For example, when annotating a field with validation prior to being inserted into the database is not possible. The only alternative to use a custom resolver for almost every field. This poses the problem that all graphql schema declarations would need to be annotated with neo4j_ignore
thus rendering the entire makeAugmentedSchema
almost obsolete.
Would be nice to some short examples. Thanks for the work! :)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.