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
Related
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(),
})
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)
}
I have a problem building NextJs Web with NextAuth. I created my own API in pages/api and protected it with getSession from NextAuth. When I call the API using getServerSideProps or getStaticProps, getSession returns a null value, but when I call the API inside a component function, getSession returns the user data value. Can anyone help or have any tips?
pages/index.jsx
export async function getStaticProps(context) {
const projects = await fetchApi(`${process.env.BASE_URL}/projects`);
console.log(projects)
return {
props: {},
};
}
pages/api/projects
import { getSession } from 'next-auth/client';
async function handler(req, res) {
const projectService = new ProjectService();
let session;
let emailUser;
try {
session = await getSession({ req });
console.log(session); // null
emailUser = session.user.email;
if (!session) {
throw new AuthenticationError('No authenticated');
}
} catch (error) {
if (error instanceof ClientError) {
return res.status(error.statusCode).json(clientErrRes(error));
}
return res.status(500).json(serverErrRes(error));
}
...another code
}
You need to add headers in the fetch from the context. because you are fetching from server side.
const {req}=context
const {cookie} = req.headers
return fetch(`${process.env.BASE_URL}/projects`, {
headers: {
'Cookie':cookie
}
})
You can't have auth (per user) in getStaticProps because those pages are generated at compile time.
When you are calling the api from the react component (at runtime - from the browser) you are doing it on the behalf of the user so there is a session (cookie) there.
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.
I am using the latest version of Apollo Client in a simple React app and I am trying to pull out a header value from the response that is being used to show the size of the record set being returned.
I appreciate that this is not the most elegant way of providing the result set size, but that is how the API has currently been set up.
I was hoping to use the the middleware type options to do this, but when I inspect the response object I can't seem to pull out any headers.
The network trace does show that the response header is as expected so I suspect I am misunderstanding how to get at the underlying objects that I need.
I have checked the documentation, but nothing stands out as obvious hence the question here ...
When the backend responds, the headers should include:
Access-Control-Expose-Headers: * // or the name of your refreshToken field
Here you have the full code:
Front-end: (Apollo & React)
const httpLink = new HttpLink({ uri: URL_SERVER_GRAPHQL })
// Setup the header for the request
const middlewareAuthLink = new ApolloLink((operation, forward) => {
const token = localStorage.getItem(AUTH_TOKEN)
const authorizationHeader = token ? `Bearer ${token}` : null
operation.setContext({
headers: {
authorization: authorizationHeader
}
})
return forward(operation)
})
// After the backend responds, we take the refreshToken from headers if it exists, and save it in the cookie.
const afterwareLink = new ApolloLink((operation, forward) => {
return forward(operation).map(response => {
const context = operation.getContext()
const { response: { headers } } = context
if (headers) {
const refreshToken = headers.get('refreshToken')
if (refreshToken) {
localStorage.setItem(AUTH_TOKEN, refreshToken)
}
}
return response
})
})
const client = new ApolloClient({
link: from([middlewareAuthLink, afterwareLink, httpLink]),
cache: new InMemoryCache()
})
In the backend (express).
If we need to refresh the token (e.g: because the actual one is going to expire)
const refreshToken = getNewToken()
res.set({
'Access-Control-Expose-Headers': 'refreshToken', // The frontEnd can read refreshToken
refreshToken
})
Documentation from: https://www.apollographql.com/docs/react/networking/network-layer/#afterware
Found the answer here: https://github.com/apollographql/apollo-client/issues/2514
Have to access it via the operation context ... interestingly the dev tools appears to show that the headers object is empty, but you can then pull named headers from it ...
const afterwareLink = new ApolloLink((operation, forward) => {
return forward(operation).map(response => {
const context = operation.getContext();
const { response: { headers } } = context;
if (headers) {
const yourHeader = headers.get('yourHeader');
}
return response;
});
});