bokjo
(Bojanche)
March 1, 2021, 4:30pm
1
Hello everybody,
opened 09:45AM - 01 Mar 21 UTC
Hello there,
I have an `augmentedSchema` wrapped in `buildFederatedSchema` frโฆ om `@apollo/federation` but the Authorization directives are ignored when the API is called through the Gateway and directly.
When the API is created and called directly with an `augmentedSchema` passed to the Apollo server instance the Authorization directives work as expected!
Fairly new to GraphQL so don't know whether this is for here or on the Apollo Federation side, don't know how to properly test and debug it, to be honest!
neo4j-graphql-js API:
```ts
import neo4j, { Driver } from "neo4j-driver";
import { makeAugmentedSchema, assertSchema } from "neo4j-graphql-js";
import { resolvers, typeDefs, config as gqlConfig } from "./testSchema";
import { buildFederatedSchema } from "@apollo/federation";
//...
const driver: Driver = neo4j.driver(
`${config.scheme}://${config.host}:${config.port}`,
neo4j.auth.basic(config.username, config.password),
);
//...
const augmentedSchema = makeAugmentedSchema({
typeDefs,
resolvers,
config: {
...gqlConfig,
experimental: true,
auth: {
isAuthenticated: true,
hasRole: true,
hasScope: true,
},
isFederated: true,
},
});
const schema = buildFederatedSchema([augmentedSchema]);
const server = new ApolloServer({
context: ({ req }) => {
return { driver, neo4jDatabase: config.database, req };
},
// schema: augmentedSchema,
schema,
playground: true,
introspection: true,
});
```
Apollo Federation Gateway:
```js
const { ApolloServer } = require("apollo-server");
const { ApolloGateway, RemoteGraphQLDataSource } = require("@apollo/gateway");
const gateway = new ApolloGateway({
serviceList: [
{
name: "Svc1",
url: "http://localhost:5000/graphql"
},
{
name: "Svc2",
url: "http://localhost:5001/graphql"
}
],
buildService({ name, url }) {
return new RemoteGraphQLDataSource({
url,
willSendRequest({ request, context }) {
request.http.headers = context.req.headers
}
});
}
});
(async () => {
const { schema, executor } = await gateway.load();
const server = new ApolloServer({
context: ({ req }) => {
return { req };
},
schema,
executor
});
server.listen({ port: process.env.PORT || 4000 }).then(({ url }) => {
console.log(`๐ Server ready at ${url}`);
});
})();
```
My `SVC1` has a simple resolver that returns hello world string and it has the @isAuthenticated directive (just as an example, there are other customs and autogenerated queries and mutations of course! )
```js
type Query {
sayHello: String @isAuthenticated
}
export const resolvers = {
Query: {
sayHello: (parent, args, context, info) => {
console.log(parent, args, context, info);
return "Hello World";
},
},
Mutation: {},
};
```
Case 1:
- If you run the "SVC1" API Apollo server directly with the `augmented` schema then the Authorization directive is respected and working as expected
Case 2:
- if you run the "SVC1" API Apollo server with the federated schema and call the SVC1 API tough the Gateway then the Authorization directive is ignored and the data is returned without providing the JWT token in the authorization header
p.s the `Apollo Federation Gateway` is properly propagating the request with the Authorization header to the SVC1!
This morning I created this issue on GitHub regarding the Authentication directive being ignored for GraphQL Grand stack federated augmented schema called through the Gateway.
In short:
running the Apollo GraphQL Server directly with the augmented schema the @isAuthenticated directive works as expected
running the Apollo GraphQL Server with federated schema based on the augmented one and calling the API through the Gateway doesn't work and the @isAuthenticated directive is not respected (you can call the API without providing JWT into the Authorization header )
p.s same behaviour if you call the API directly and not via the Gateway
For more details and code examples please refer to the GitHub issue above.
Any general input on this and how to debug it is more than welcome.
Thank you
Bojanche S.
uday
(Uday Korlimarla)
March 2, 2021, 7:23pm
2
Here is a snipper I have. I need my API to be used always when authorised. So connection to database can be established only when there is a valid JWT
Starting the Apollo server -Snippet
const server = new ApolloServer({
context: ({ req }) => {
if(req.headers['user-agent'].startsWith('auth0-logstream')) {
req.body.logs.forEach(log => {
// authLogger.log(log);
})
}
const realIp = req.headers['x-real-ip'];
// logger.info({query: req.body.query, variables: req.body.variables, clientIp: realIp});
/*
Order of operations - All must pass
1. Decode Session Token
2. Verify Session Token
3. Get Email of the user
*/
let sessionVerified = false;
if(req.headers.authorization === undefined || req.headers.authorization === null) {
// logger.error({'type':'unauthenticated', clientIp: realIp})
throw new AuthenticationError('Unauthorized, Must Authenticate to access this resource.');
}
const decodedToken = jwt_decode(req.headers.authorization.replace('Bearer ', ''));
const decoded = jwt.verify(req.headers.authorization.replace('Bearer ', ''), process.env.JWT_SECRET);
if(decoded) {
sessionVerified = true;
}
// Locate the email thingy from JWT
const email = decodedToken['https://authdomain.io/email'] ? decodedToken['https://authdomain.io/email'] : null;
if(email === null || email === undefined) {
throw new AuthenticationError("Oye mate, You're naughty.")
}
return {
driver,
// DB name on the server - using the email
neo4jDatabase: email.split('@')[1].replace('.', ''),
headers: req.headers,
token: req.headers.token,
cypherParams: {
variables: req.body.variables,
email
},
req
}
},
engine: {
reportSchema: true
},
schema: augmentedSchema,
introspection: true,
playground: false,
});
// Starting the server
server.listen(4000, '0.0.0.0')
.then(({ url }) => {
console.log(\`๐ GraphQL API is ready at ${url}\`);
})
.catch(err => {
console.error('Failed to start server');
console.error('--Details--');
console.dir(err);
})%
Schema: Here is a sample schema for a Node where user can read or delete only. These are permissions that come from JWT. I hope this helps.
type Bojanche @hasScope(scopes: ["Bojanche: Read", "Bojanche: Delete"]) {
id: ID @index
name: String! @index
}
uday
(Uday Korlimarla)
March 2, 2021, 7:34pm
3
In my case, I am using Auth0
and starting a GrandStack with Auth0 is quite easy. I think what you are missing is using the hasScope directives and hasRoles directives.
bokjo
(Bojanche)
March 3, 2021, 8:34am
4
Hello @uday
Thanks for the reply.
As I mentioned @isAuthenticared is working fine in the standalone API created with the augmented schema.
The problem comes when I convert the same augmentedSchema to a federated one in order to be used through Apollo Federation Gateway.
const schema = buildFederatedSchema([augmentedSchema]);
At this point calling the service directly or through the gateway ignores the @isAuthenticared , despite that the request is valid and can have or not the proper JWT.
Kind Regards,
Bojanche S.
uday
(Uday Korlimarla)
March 16, 2021, 11:47am
5
@bokjo I actually took time to learn about Apollo Gateway. I do not have an answer for you as I have no clue mate.
Did you read this on grandstack? neo4j-graphql-js/apollo-federation.md at master ยท neo4j-graphql/neo4j-graphql-js ยท GitHub