I have two graphql endpoints (authentiaction endpoint [AUTH] and application endpoint [APP]).
I created the api gateway using "makeRemoteExecutableSchema" and "introspectSchema".
The thing is that [AUTH] endpoint on login mutation sets a cookie:
res.cookie("token", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days
});
The thing is, when I make a request directly to the [AUTH] endpoint, the cookie is set (I can see "Set-cookie" response headers). But when I make a login mutation via API gateway, the cookie is not set. How to solve my issue?
Related
Have really been struggling with CORs lately, and finally got a combination that works. However based on all of the documentation I have read, it should NOT work.
From the "Configuring CORS" documentation on Apollo GraphQL:
Pass the credentials option e.g. credentials: 'same-origin' if your
backend server is the same domain, as shown below, or else
credentials: 'include' if your backend is a different domain.
Since my front end is on EC2 and Backend is on Lambda, they are on different origins no?
My stack:
Frontend: Apollo Client / NextJS (hosted on EC2) ex: http://mysite.io
Gateway: AWS API Gateway ex: https://XXXXXXXXX.execute-api.us-east-1.amazonaws.com/dev/graphql
Backend: Apollo Server GraphQL (hosted on Lambda)
Note: the below code works.
Frontend:
const link = createHttpLink({
uri: process.env.API_URL,
credentials: 'same-origin',
headers: {
'x-api-key': process.env.API_KEY
}
});
Gateway Settings:
Backend:
exports.handler = server.createHandler({
expressGetMiddlewareOptions: {
cors: {
origin: true,
credentials: true
}
},
});
But if I change my credential value to 'include' on the apollo client (front end)...
We run into the following preflight error:
Response to preflight request doesn't pass access control check: 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'.
I have frontend client running on custom Next.js server that is fetching data with apollo client.
My backend is graphql-yoga with prisma utilizing express-session.
I have problem with picking correct CORS settings for my client and backend so cookie would be properly set in the browser, especially on heroku.
Currently I am sending graphql request from client with apollo-client having option
credentials: "include" but also have tried "same-origin" with no better result.
I can see cookie client in response from my backend in Set-Cookie header, and in devTools/application/cookies. But this cookie is transparent to browser and is lost on refresh.
With this said I also tried to implement some afterware to apollo client as apolloLink that would intercept cookie from response.headers but it is empty.
So far now I'm thinking about pursuing those two paths of resolving the issue.
(I'm only implementing CORS because browser prevents fetching data without it.)
CLIENT
ApolloClient config for client-side:
const httpLink = new HttpLink({
credentials: "include",
uri: BACKEND_ENDPOINT,
});
Client CORS usage and config:
app
.prepare()
.then(() => {
const server = express()
.use(cors(corsOptions))
.options("*", cors(corsOptions))
.set("trust proxy", 1);
...here goes rest of implementation
const corsOptions = {
origin: [process.env.BACKEND_ENDPOINT, process.env.HEROKU_CORS_URL],
credentials: true,
allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With", "X-Forwarded-Proto", "Cookie", "Set-Cookie", '*'],
methods: "GET,POST",
optionsSuccessStatus: 200
};
My atempt to get headers from response in apolloClient(but headers are empty and data is not fetched afterwards):
const middlewareLink = new ApolloLink((operation, forward) => {
return forward(operation).map(response => {
const context = operation.getContext();
const {response: {headers}} = context;
if (headers) {
const cookie = response.headers.get("set-cookie");
if (cookie) {
//set cookie here
}
}
return response;
});
});
BACKEND
CORS implementaion (remeber that is gql-yoga so I need first to expose express from it)
server.express
.use(cors(corsOptions))
.options("*", cors())
.set("trust proxy", 1);
...here goes rest of implementation
const corsOptions = {
origin: [process.env.CLIENT_URL_DEV, process.env.CLIENT_URL_PROD, process.env.HEROKU_CORS_URL],
credentials: true,
allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With", "X-Forwarded-Proto", "Cookie", "Set-Cookie"],
exposedHeaders: ["Content-Type", "Authorization", "X-Requested-With", "X-Forwarded-Proto", "Cookie", "Set-Cookie"],
methods: "GET,HEAD,PUT,PATCH,POST,OPTIONS",
optionsSuccessStatus: 200
};
Session settings, store is connect-redis
server.express
.use(
session({
store: store,
genid: () => uuidv4(v4options),
name: process.env.SESSION_NAME,
secret: process.env.SESSION_SECRET,
resave: true,
rolling: true,
saveUninitialized: false,
sameSite: false,
proxy: STAGE,
unset: "destroy",
cookie: {
httpOnly: true,
path: "/",
secure: STAGE,
maxAge: STAGE ? TTL_PROD : TTL_DEV
}
})
)
I am expecting session cookie to be set on the client after authentication.
Actual result:
Cookie is visible only in Set-Cookie response header but is transparent to browser and not persistent nor set (lost on refresh or page change). Funny enough I can still make authenticated requests for data.
This may not be a CORS issue, it looks like a third-party cookie problem.
Behaviour could be different across browsers so I recommend testing several ones. Firefox (as of version 77) seems to be less restrictive. In Chrome (as of version 83) there is an indicator on the far right of the URL bar when a third party cookie has been blocked. You can confirm whether third party cookies is the cause of the problem by creating an exception for the current website.
Assuming your current setup is as follows:
frontend.com
backend.herokuapp.com
Using a custom domain for your backend that is a subdomain of your frontend would solve your problem:
frontend.com
api.frontend.com
The following setup wouldn't work because herokuapp.com is included in the Mozilla Foundation’s Public Suffix List:
frontend.herokuapp.com
backend.herokuapp.com
More details on Heroku.
I am trying to implement authentication in Graphql using firebase and websockets (on react native).
The client uses the firebase to authenticate and gets a token. It then sends the token to the server over a websocket client, which validates the user using the admin sdk.
I am facing two problems:
When the app boots up, it establishes a ws connection which by that time, it has no authorization header. The user gets a token after a while using firebase.
The token expires after some time, so after a while I need to update the authorization header in the websocket connection, and re-run the query, mutation or subscription which got rejected because of the expired token.
Is there a way to update the authorization header and re-run the query?
Do I need to close the previous connection and open a new one using the new token in the authorization header? How is this done?
I am using apollo-server, apollo-client, apollo-link, subscriptions-transport-ws.
I haven't run into your exact issue before, but you should check out connectionParams field. If on startup a new websocket client is created, you can fetch a new token asynchronously in the connectionParams.
import { createClient } from 'graphql-ws';
export const createWebsocketClient = (user) => createClient({
url: 'ws://localhost:8080/v1/graphql',
connectionParams: async () => {
const token = await user.getToken();
return {
headers: {
Authorization: `Bearer ${token}`,
},
};
},
});
The token is only sent when initializing the connection, so even if the token expires after the initializing, it shouldn't be a problem.
I have a Django app that authenticates using Okta:
headers = {
'Authorization': 'SSWS {}'.format(<okta api token>),
'Accept': 'application/json',
'Content-Type': 'application/json'
}
authentication_payload = {
'username': <username>,
'password': <password>
}
response = requests.post(
<okta auth endpoint>,
headers=headers,
data=json.dumps(authentication_payload)
)
This works successfully. From the response content I am able to get the User Id:
content = json.loads(r.content.decode('utf-8'))
okta_user_id = content['_embedded']['user']['id']
I then use the okta_user_id to create the endpoint to get the okta user by id:
okta_user_endpoint = https://<org>.okta.com/api/v1/users/<okta_user_id>
I then use the same headers from the authentication call, with the same api token, and try to get the user by id:
user_response = requests.get(
okta_user_endpoint,
headers=headers
)
But this is unsuccessful. I get a 401 error with the following content:
{
"errorCode":"E0000011",
"errorSummary":"Invalid token provided",
"errorLink":"E0000011",
"errorCauses":[]
}
Seems straight forward with an invalid token, but if the token is invalid how am I able to successfully make the authentication call? And if the token if valid for the authentication call why is it not working to get the user by id?
Okta recently changed the way that the /authn endpoint works. The /authn endpoint no longer requires an authentication token. This was done in order to support single-page applications.
It looks like your application will need to be able to fetch user information on an arbitrary user. In that case, using an Okta API token makes sense.
However, if you were making that call from a single-page application, you would want to make a request to the /users/me API endpoint.
I'm using express.js and mongoStore and csrf in express.js
and I want to maintain the login session for 24 hours.
so my express configuration file is like this.
// express/mongo session storage
app.use(express.session({
secret: pkg.name,
store: new mongoStore({
url: config.db,
collection : 'sessions',
auto_reconnect: true
}),
cookie:{
maxAge : new Date(Date.now() + 3600000*24) //1 Hour = 3600000
}
}))
// adds CSRF support
app.use(express.csrf())
and it works maintaining login session for 24 hours.
the problem is csrf session token also change.
Thus, after 24 hours from first login, csrf error occur on my website.
is there any way to maintaining user login session without csrf error?
thanks in advance!
:D