Can't send an httpOnly cookie with axios or fetch [duplicate] - session

Cookies are not sent to the server via getServerSideProps, here is the code in the front-end:
export async function getServerSideProps() {
const res = await axios.get("http://localhost:5000/api/auth", {withCredentials: true});
const data = await res.data;
return { props: { data } }
}
On the server I have a strategy that checks the access JWT token.
export class JwtStrategy extends PassportStrategy(Strategy, "jwt") {
constructor() {
super({
ignoreExpiration: false,
secretOrKey: "secret",
jwtFromRequest: ExtractJwt.fromExtractors([
(request: Request) => {
console.log(request.cookies) // [Object: null prototype] {}
let data = request.cookies['access'];
return data;
}
]),
});
}
async validate(payload: any){
return payload;
}
}
That is, when I send a request via getServerSideProps cookies do not come to the server, although if I send, for example via useEffect, then cookies come normally.

That's because the request inside getServerSideProps doesn't run in the browser - where cookies are automatically sent on every request - but actually gets executed on the server, in a Node.js environment.
This means you need to explicitly pass the cookies to the axios request to send them through.
export async function getServerSideProps({ req }) {
const res = await axios.get("http://localhost:5000/api/auth", {
withCredentials: true,
headers: {
Cookie: req.headers.cookie
}
});
const data = await res.data;
return { props: { data } }
}
The same principle applies to requests made from API routes to external APIs, cookies need to be explicitly passed as well.
export default function handler(req, res) {
const res = await axios.get("http://localhost:5000/api/auth", {
withCredentials: true,
headers: {
Cookie: req.headers.cookie
}
});
const data = await res.data;
res.status(200).json(data)
}

Related

Apollo Client GraphQL: When getting FORBIDDEN error, automatically get new JWT AccessToken and RefreshToken. How does the logic work?

In the following code, you can see that I am creating an errorLink. It makes use of an observable, a subscriber and then it uses this forward() function.
Can someone explain to me what's exactly happening here. I am bit familiar with observables, but I cannot understand what's going on here.
When creating the observable, where does the observer argument come from?
I would love to dive a bit deeper.
Also, why is bind used, when creating the subscriber?
const errorLink = onError(
({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
for (let err of graphQLErrors) {
switch (err.extensions.code) {
case "FORBIDDEN":
console.log("errs!")
// ignore 401 error for a refresh request
if (operation.operationName === "RehydrateTokens") return
const observable = new Observable<FetchResult<Record<string, any>>>(
(observer) => {
console.log(observer)
// used an annonymous function for using an async function
;(async () => {
try {
console.log("yop bin hier")
const accessToken = await refreshToken()
console.log("AT!", accessToken)
if (!accessToken) {
throw new GraphQLError("Empty AccessToken")
}
// Retry the failed request
const subscriber = {
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer),
}
forward(operation).subscribe(subscriber)
} catch (err) {
observer.error(err)
}
})()
}
)
return observable
}
}
}
if (networkError) console.log(`[Network error]: ${networkError}`)
}
)
Just so that you are understanding the context.
Iam combining mutliple apollo links.
const httpLink = createHttpLink({
uri: "http://localhost:3000/graphql",
})
// Returns accesstoken if opoeration is not a refresh token request
function returnTokenDependingOnOperation(operation: GraphQLRequest) {
if (isRefreshRequest(operation)) {
return localStorage.getItem("refreshToken")
} else return localStorage.getItem("accessToken")
}
const authLink = setContext((operation, { headers }) => {
let token = returnTokenDependingOnOperation(operation)
console.log("tk!!!", token)
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
},
}
})
const client = new ApolloClient({
link: ApolloLink.from([errorLink, authLink, httpLink]),
cache: new InMemoryCache(),
})

Applying cookie returned from REST api call via GraphQL

When implementing an auth GraphQL module a resolver calls an external API via RESTDataSource.
async logout() {
return await this.post('/logout', { credentials: 'include' })
}
This return an empty body object (expected) and headers that include * Set-Cookie: access_token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT
The expected result is that the access_token cookie is deleted from the browser. If this is called directly using fetch then it happens but not when done via GraphQL.
Here is the setup:
# Resolver
Mutation: {
logout: async (_parent, _args, context) => {
const api = context.injector.get(UserAPI);
const response = await api.logout();
return response;
},
},
and the server...
# Apollo Server
function apolloGraphqlServer(app: Express) {
const server = new ApolloServer({
schema,
context: ({ req }): { token: string } => {
return {
token: req.headers.authorization,
};
},
introspection: true,
});
server.applyMiddleware({ app });
}
and the client (on Express)
const apolloClient = new ApolloClient({
cache: new InMemoryCache(),
link: new SchemaLink({
schema,
context: {
session: req,
token: authCookie ? `Bearer ${authCookie}` : null,
},
}),
credentials: 'include',
ssrMode: true,
});
How do I get the cookie to delete given the response
Solved this with passing the response into the context...
const server = new ApolloServer({
schema,
context: ({ req, res }): { token: string; res: Response } => {
return {
token: req.headers.authorization,
res,
};
},
});
and then in the datasource, intercepting the received response and setting the header if available.
# datasource
didReceiveResponse(response: Response, request: Request) {
if (response.headers.get('set-cookie')) {
this.context.res.set('set-cookie', response.headers.get('set-cookie'));
}
return super.didReceiveResponse(response, request);
}
Not sure if this is the most elegant solution but works for now

401 - unauthorized call to Twitch Api from nextjs

I have this code:
const getToken = async () => {
return Axios.post(
`https://id.twitch.tv/oauth2/token?client_id=${process.env.TWITCH_ID}&client_secret=${process.env.TWITCH_SECRET}&grant_type=client_credentials`
).then((res) => res.data["access_token"]);
};
const getId = async (accessToken, session) => {
const response = await Axios.get(
`https://api.twitch.tv/helix/users?login=${session.user.name}`,
{
Authorization: `Bearer ${accessToken}`,
"Client-Id": process.env.TWITCH_ID,
}
);
return response.data.id;
};
export async function getServerSideProps(context) {
const session = await getSession(context);
if (session) {
const accessToken = await getToken();
console.log(accessToken);
const id = await getId(accessToken, session);
console.log(id);
}
return {
props: {}, // will be passed to the page component as props
};
}
This is Next.js function that will do this on every request.
I am using Next.js, next-auth for authentication.
Everything should work fine, even on line console.log(accessToken) I get the expected output. But in function getId it says 401 - unauthorized.
I am calling Twitch api.

Decorate api requests with Bearer token in NextJS getServerSideProps

I am using axios interceptors to decorate all my requests with an Authorization header.
I get the token from #auth0/nextjs-auth0 by using an API route /api/token:
import auth0 from '#/libs/auth0/auth0';
import { AccessTokenResponse } from '#auth0/nextjs-auth0/dist/tokens/token-cache';
import { NextApiRequest, NextApiResponse } from 'next';
export async function getAccessToken(
req: NextApiRequest,
res: NextApiResponse
): Promise<AccessTokenResponse> {
const tokenCache = auth0(req).tokenCache(req, res);
return tokenCache.getAccessToken();
}
export default async function token(
req: NextApiRequest,
res: NextApiResponse
): Promise<void> {
try {
const { accessToken } = await getAccessToken(req, res);
res.status(200).end(accessToken);
} catch (error) {
res.status(error.status || 500).end(error.message);
}
}
The api route works fine on the client side requests and I can see the token being added and requests going through.
const { token } = await axios.get('/api/token');
const result = {
...config,
headers: {
Authorization: `Bearer ${data}`,
},
};
return result;
However this does not work for the SSR pages when I use the same axios interceptors.
I tried full qualifying the url like so:
const { token } = await axios.get('http://localhost:3000/api/token');
But this throws an error in the SSR layer:
The user does not have a valid session.
I do not know how to get the token from the token cache to be used in the interceptor for the api calls made via SSR?
Any ideas or similar experience?
I opted to store the token as a cookie for all requests and access it through the req key in the context parameter when using SSR:
export const getServerSideProps: GetServerSideProps = async ({ req }) {
const accessToken = req.cookies.token
// Remaining code
}
https://nextjs.org/docs/api-reference/data-fetching/get-server-side-props

Write on session with Sapper and Svelte

I wrote a Sapper app with session management following the RealWorld example:
polka()
.use(bodyParser.json())
.use(session({
name: 'kidways-app',
secret: 'conduit',
resave: false,
saveUninitialized: true,
cookie: {
maxAge: 31536000
},
store: new FileStore({
path: 'data/sessions',
})
}))
.use(
compression({ threshold: 0 }),
sirv('static', { dev }),
pdfMiddleware,
sapper.middleware({
session: req => ({
token: req.session && req.session.token
})
})
)
.listen(PORT, err => {
if (err) console.log('error', err);
});
Then on my _layout.sevlte:
<script context="module">
export async function preload({ query }, session) {
console.log('preload', session)
return {
// ...
};
}
</script>
<script>
import { onMount, createEventDispatcher } from 'svelte';
import { Splash } from 'project-components';
import * as sapper from '#sapper/app';
import { user } from '../stores';
import client from '../feathers';
const { session } = sapper.stores();
onMount(async () => {
try {
await client.reAuthenticate();
const auth = await client.get('authentication');
user.set(auth.user);
$session.token = 'test';
} catch (e) {
} finally {
loaded = true;
}
});
console.log($session)
</script>
<h1>{$session.token}</h1>
This work on client side rendering, but the token is still undefined on preload, making my SSR template rendering broken.
What did I missed?
When a page renders, session is populated according to the return value of the function you specified here:
sapper.middleware({
session: req => ({
token: req.session && req.session.token
})
})
So while the client may have an up-to-date token, it won't take effect on page reload unless you somehow persist the token to the server in such a way that the session middleware knows about it.
Typically you'd achieve this by having a server route, like routes/auth/token.js or something...
export function post(req, res) {
req.session.token = req.body.token;
res.writeHead(200, {
'Content-Type': 'application/json'
});
res.end();
}
...and posting the token from the client:
onMount(async () => {
try {
await client.reAuthenticate();
const auth = await client.get('authentication');
user.set(auth.user);
await fetch(`auth/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ token })
});
// writing to the session store on the client means
// it's immediately available to the rest of the app,
// without needing to reload the page
$session.token = 'test';
} catch (e) {
} finally {
loaded = true;
}
});

Resources