Hey, I'm trying to setup Apollo graphql + Nest.js by following the guide, and I manage to get the se...
i
Hey, I'm trying to setup Apollo graphql + Nest.js by following the guide, and I manage to get the session info on a controller using the Auth guard and Session param decorator (from the guide) but not on a resolver. I tried (with inspiration from the graphql integration guide) to use a CanActivate guard and access the session with
GqlExecutionContext.create(context)
and also tried injecting the session param to the resolver with
Copy code
export const User = createParamDecorator(
  (data: unknown, ctx: ExecutionContext) => {
    const x = GqlExecutionContext.create(ctx);
    const context = x.getContext();
    return context.session;
  },
);
with no success and couldn't find any code example that combines graphql + nest
r
Hey!
We don’t have a code example of this yet. Perhaps @porcellus can help out with this tomorrow sometime.
In the meantime, have a look at this issue if ur helps: https://github.com/supertokens/supertokens-node/issues/331
p
hi, yeah, I'd recommend the same thing as back then. Hopefully we'll get around to building that apollo plugin soon. Until then, the same thing should work.
let me know if I can provide more help
i
@porcellus @rp_st thanks for the quick reply! i tried implementing that responseForOperation plugin while using node-fetch for the new Headers statement (it threw an error for not recognizing it otherwise) but didn't manage to get the session info - tried logging
console.log('requestForST.session: ', requestForST.session);
on the plugin
I think i'm doing something wrong, when I set sessionRequired to true, i get 401 unauthorized response, although i go through the login screen successfully
p
Headers from node-fetch should work, that's what apollo uses internally I think.
you could try enabling additional logging like this: https://supertokens.com/docs/passwordless/troubleshooting/how-to-troubleshoot
i
@porcellus thanks for the link, it adds a lot of clarity! 1. so, i'm getting these logs on the server
Copy code
message: "middleware: requestRID is: anti-csrf"
"middleware: Checking recipe ID for match: thirdpartyemailpassword"
"middleware: Checking recipe ID for match: session"
"middleware: Not handling because no recipe matched"
the first log led me here
https://github.com/supertokens/supertokens-node/issues/202
so i tried manually setting the rid header to
thirdpartyemailpassword
2. now i'm getting
"middleware: Not handling because request path did not start with config path. Request path: /graphql"
where my appInfo config has
apiBasePath: '/api/auth'
and my request goes to
/graphql
so I changed my server path to
/api/auth/graphql
3. i'm getting a match
Copy code
"middleware: requestRID is: thirdpartyemailpassword"
"middleware: Matched with recipe ID: thirdpartyemailpassword"
"middleware: Not handling because recipe doesn't handle request path or method. Request path: /api/auth/graphql, request method: post"
not sure how to move forward
p
are those all the logs you are getting? the middleware is not supposed to handle the graphql request, so that seems to be correct. it only handles stuff like the sign-in/up APIs.
I think you should check logs by
verifySession
. I'd start by putting logs (kind of like separators) before and after the
verifySession
call and checking what happens there.
i
ok, i reverted my client to make graph requests to
/graphql
while my apiBasePath is
/api/auth
I log in with google successfully, but I don't manage to get the session info neither on the first login cycle nor when i refresh the client - here's the full log for clarity:
first login flow :
refresh token flow (hitting refresh on client)
Copy code
com.supertokens {t: "2022-08-29T07:11:20.481Z", message: "middleware: Started", file: "/Users/idanhaviv/Developer/yooz-server/node_modules/supertokens-node/lib/build/supertokens.js:224:26" sdkVer: "11.2.0"} +0ms

  com.supertokens {t: "2022-08-29T07:11:20.481Z", message: "middleware: Not handling because request path did not start with config path. Request path: /graphql", file: "/Users/idanhaviv/Developer/yooz-server/node_modules/supertokens-node/lib/build/supertokens.js:231:30" sdkVer: "11.2.0"} +0ms

  com.supertokens {t: "2022-08-29T07:11:20.483Z", message: "getSession: Started", file: "/Users/idanhaviv/Developer/yooz-server/node_modules/supertokens-node/lib/build/recipe/session/recipeImplementation.js:135:26" sdkVer: "11.2.0"} +0ms

  com.supertokens {t: "2022-08-29T07:11:20.483Z", message: "getSession: rid in header: true", file: "/Users/idanhaviv/Developer/yooz-server/node_modules/supertokens-node/lib/build/recipe/session/recipeImplementation.js:136:26" sdkVer: "11.2.0"} +0ms

  com.supertokens {t: "2022-08-29T07:11:20.483Z", message: "getSession: request method: post", file: "/Users/idanhaviv/Developer/yooz-server/node_modules/supertokens-node/lib/build/recipe/session/recipeImplementation.js:137:26" sdkVer: "11.2.0"} +0ms

  com.supertokens {t: "2022-08-29T07:11:20.484Z", message: "getSession: returning undefined because idRefreshToken is undefined and sessionRequired is false", file: "/Users/idanhaviv/Developer/yooz-server/node_modules/supertokens-node/lib/build/recipe/session/recipeImplementation.js:144:34" sdkVer: "11.2.0"} +0ms
and when i change sessionRequired to true I get an infinite loop of refresh trials with this log (short version because it's too long)
Copy code
com.supertokens {t: "2022-08-29T07:20:16.760Z", message: "getSession: UNAUTHORISED because idRefreshToken from cookies is undefined", file: "/Users/idanhaviv/Developer/yooz-server/node_modules/supertokens-node/lib/build/recipe/session/recipeImplementation.js:151:30" sdkVer: "11.2.0"} +0ms

  com.supertokens {t: "2022-08-29T07:20:16.760Z", message: "errorHandler: Started", file: "/Users/idanhaviv/Developer/yooz-server/node_modules/supertokens-node/lib/build/supertokens.js:315:26" sdkVer: "11.2.0"} +0ms

  com.supertokens {t: "2022-08-29T07:20:16.760Z", message: "errorHandler: Error is from SuperTokens recipe. Message: Session does not exist. Are you sending the session tokens in the request as cookies?", file: "/Users/idanhaviv/Developer/yooz-server/node_modules/supertokens-node/lib/build/supertokens.js:317:30" sdkVer: "11.2.0"} +0ms
am I supposed to send the session tokens as cookies explicitly? as far as i understood from the frontend guide, SuperTokens intercepts fetch automatically and i don't need to intervene
p
It does, but it mainly just adds the rid header. The browser itself should be sending the cookies if everything is configured correctly
Can you check the cookies saved in the browser?
Are the frontend/backend on the same domain?
What browser are you using?
i
these are the cookies i have in the browser (remove irrelevant ones):
Copy code
sIRTFrontend
sFrontToken
sAccessToken
ajs_user_id
sIdRefreshToken
ajs_anonymous_id
they're not on the same domain, i'm currently running on localhost:3000 for FE and localhost:4000 for the BE
and i'm using Chrome
p
hmm. can you check what cookies were sent with the request? and maybe compare them to the headers object in
requestForST
i
how can i check which cookies are sent with a request? you mean which headers are sent? sent request headers:
Copy code
accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Content-Length: 342
content-type: application/json
Host: localhost:4000
Origin: http://localhost:3000
Referer: http://localhost:3000/
rid: anti-csrf
sec-ch-ua: "Chromium";v="104", " Not A;Brand";v="99", "Google Chrome";v="104"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36
requestForST:
Copy code
requestForST:  {
  headers: {
    accept: '*/*',
    'accept-encoding': 'gzip, deflate, br',
    'accept-language': 'en-US,en;q=0.9',
    connection: 'keep-alive',
    'content-length': '342',
    'content-type': 'application/json',
    host: 'localhost:4000',
    origin: 'http://localhost:3000',
    referer: 'http://localhost:3000/',
    rid: 'anti-csrf',
    'sec-ch-ua': '"Chromium";v="104", " Not A;Brand";v="99", "Google Chrome";v="104"',
    'sec-ch-ua-mobile': '?0',
    'sec-ch-ua-platform': '"macOS"',
    'sec-fetch-dest': 'empty',
    'sec-fetch-mode': 'cors',
    'sec-fetch-site': 'same-site',
    'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'
  },
  method: 'POST',
  url: '/'
}
p
yep, there should be a cookie header there, they are not sent for some reason. in the saved cookies what domain does the sIdRefreshToken have?
i
localhost
p
are you setting
credentials
to
include
in the fetch options? (https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters)
i
bingo! they were set to same-origin, great catch! now i'm having CORS issue although i set it to enable cors
Copy code
app.enableCors({
    origin: ['http://localhost:3000'],
    allowedHeaders: ['content-type', ...supertokens.getAllCORSHeaders()],
    credentials: true,
  });
but i do get some session info printed now!
p
well, the backend gets the right request it's just that the frontend can't use the response because of cors settings
btw, the error in the browser console should tell you more about why it failed.
i
it shows this error
localhost/:1 Access to fetch at 'http://localhost:4000/graphql' from origin 'http://localhost:3000' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
even though the origin i passed is only
http://localhost:3000
ok, fixed it by passing
Copy code
cors: {
        credentials: true,
        origin: true,
      },
to
GraphQLModule.forRoot
p
great πŸ™‚
i
@porcellus thank you so much for your help! i'm getting close πŸ™‚
p
happy to help πŸ™‚
i
hey, i tried adding an auth Guard that looks like this
Copy code
@Injectable()
export class GraphAuthGuard implements CanActivate {
  async canActivate(context: ExecutionContext): Promise<boolean> {
    const gqlCtx = GqlExecutionContext.create(context).getContext();
    let err = undefined;

    const resp = gqlCtx.req.res;
    await verifySession()(gqlCtx.req, resp, (res) => {
      err = res;
    });

    if (resp.headersSent) {
      throw new STError({
        message: 'RESPONSE_SENT',
        type: 'RESPONSE_SENT',
      });
    }

    if (err) {
      return false;
    }

    return true;
  }
}
so i can protect specific routes, but it throws the headersSent error, i'm assuming due to how the graphql requests behave ("Error: Cannot set headers after they are sent to the client") does the plugin implementation implementation meant to replace that? was that the motivation to use the plugin? if so, how would you split between authenticated routes to public ones? perhaps by
requestContext.operation
on
responseForOperation
instead of per resolver?
p
Hi. The auth guard can still have a role - guarding routes, not resolvers. So the plugin is not a strict replacement.
About authenticating inside resolvers and mutations: https://www.apollographql.com/docs/apollo-server/security/authentication/
i
cool, thanks!
o
Hehehehe
Im doing the same anyone knows a better aproach on this?
4 Views