Closed stevenlybeck closed 3 years ago
Hi @stevenlybeck - your use case sounds reasonable.
We don't offer tooling to do this or any official guidance since the security model of the SDK is to avoid the danger of having your Access Token stolen via XSS, by hiding your session from the browser in an HttpOnly encrypted cookie.
Adding a /pages/api/accessToken
would circumvent this security feature, but if you understand and are willing to accept the risk, there's no reason why you couldn't do that to get the scaling benefits you described.
Just be aware that the Access Token you have was issued to a confidential client, not a public client. In the Auth0 dashboard for example we have different defaults for "Token Expiration" and "Token Expiration For Browser Flows" that you might want to check.
Thanks Adam, good guidance on those token expiration defaults!
No problem @stevenlybeck - closing this, feel free to ping me if you have any other questions
Hi all,
Jumping on this ticket rather than creating a new one since we're running into the same issue and didn't really think it wise to open a separate topic.
We don't offer tooling to do this or any official guidance since the security model of the SDK is to avoid the danger of having your Access Token stolen via XSS .... but if you understand and are willing to accept the risk ....
It seems odd to me that there is no officially supported path for this functionality. More importantly, I'm a little concerned that the suggested workaround is notably "risky" and explicitly insecure.
Auth0 already supports a client-side a React library that has access to JWT access tokens. Is the suggestion that this client-side method is dangerous? If that is not the case, how come this library does not incorporate the security model of that client-side approach when using Next.js for client-side rendering?
It would be nice to have some official secure documentation on how to approach this problem. Given that Next.js is explicitly intended to be rendered on both server and client (ie., it is not "either-or"), it would seem like it's a glaring omission to not have a mechanism for clients to access tokens.
Presumably you would want to use something like the current @auth0/nextjs-auth0
for server-rendered requests and @auth0/auth0-react
for client rendered components (ideally all rolled up into one interface/library), however in my testing, it seems like as it stands, these two libraries are not capable of cooperating. While the above thread seems to explain why they do not work together from an implementation perspective, it still seems odd that this is not possible given (a) how important client-side rendering is to the Next.js architecture and (b) that theoretically auth0-react provides a safe and secure way to access auth tokens.
Anyway, it would be great to get some thoughts on this-- I'm personally not a fan of implementing workarounds that are basically considered insecure by the folks who know better about this stuff than me.
@lsegal - thanks for your feedback
Auth0 already supports a client-side a React library that has access to JWT access tokens. Is the suggestion that this client-side method is dangerous?
The security model for regular web apps is more immune to XSS than the security model for SPAs, this is just a simple fact that you should take into account when choosing between the two.
If you want to use a Regular Web App and circumvent the security model by giving ATs to the frontend, that's fine too - but you should understand the trade-offs you're making before doing it - which is why a Regular Web App SDK wont do this by default.
It would be nice to have some official secure documentation on how to approach this problem.
There is a draft proposal for tackling this problem here https://github.com/b---c/oauth-token-bff and a POC here https://github.com/adamjmcgrath/oauth-token-bff-poc
Once we're happy with the recommendations, we may look at adding the features described in the above spec to this SDK
The security model for regular web apps is more immune to XSS than the security model for SPAs, this is just a simple fact that you should take into account when choosing between the two.
I'm not sure I understand this. Can you explain in what way this is true?
My understanding is that any app that allows for operations to be performed by a user could be exploited by XSS, whether it's via a hijacked Bearer token or session cookie. AFAIK, this Next.js implementation doesn't provide any specific protection against an XSS attack accessing a server-side Next.js API route even if it's protected via withApiAuthRequired
, which means an XSS attack could still do something like the following on any given page:
// XSS attack
fetch('/api/change-password', { method: 'POST', body: 'password=newpassword' });
// in /pages/api/change-password.ts:
import { withApiAuthRequired } from "@auth0/nextjs-auth0";
import { NextApiRequest, NextApiResponse } from "next";
export default withApiAuthRequired(async function handler(req, res) {
console.log("IT WORKED!");
res.status(200).json({ result: "success" });
});
// prints:
IT WORKED!
When you talk about immunity, are you specifically referring to extracting data stored in HttpOnly cookies? That might help me frame this conversation better.
In general it seems like calling this version of the library more secure, or conversely, the auth0-react frontend library "riskier", is providing a false sense of security in this specific implementation. Actual mitigation of XSS should be coming from use of React itself (which does a pretty good job of sanitizing input), at which point both implementations should have equivalent "immunity" from most XSS attacks. The only difference should be the ease of fully extracting the token from the boundary of the page via the use of HttpOnly cookies-- though there are quite a few workarounds to this one that minimize the security of this approach. Hopefully I got this all correct.
I guess fundamentally, to take a step back here, I'm surprised there is not more cross-compatibility between these two libraries, as most Next.js applications are both server side and client side rendered. Suggesting that a Next.js app is not an SPA is a little misleading in any case that is not pure SSG (which is a very small part of Next.js usage). Next.js apps are not "regular web apps" (I'm assuming the "regular web app" metric is pure server-side rendered content with minimal frontend JS).
To put this all a little more simply, Next.js is much more of an SPA framework than a "regular web app", but it sounds like this library as treating not treating it as an SPA, and by extension leaving quite a bit of Next.js integration support on the table.
I'm not sure I understand this. Can you explain in what way this is true? My understanding is that any app that allows for operations to be performed by a user could be exploited by XSS, whether it's via a hijacked Bearer token or session cookie.
Yes, you are correct. I should have been more clear. What I should have said is that "In the security model for regular web apps your credentials are more immune to an XSS attack than the security model for SPA" (because they're stored in a HTTPOnly cookie). There is of course nothing stopping a malicious user with XSS issuing requests against an API on your behalf.
To put this all a little more simply, Next.js is much more of an SPA framework than a "regular web app", but it sounds like this library as treating not treating it as an SPA, and by extension leaving quite a bit of Next.js integration support on the table.
Yes, this SDK is very much a Regular Web Application SDK.
I appreciate this might not work exactly how you'd like, and I appreciate the feedback, I'll be sure to share it with my team.
I appreciate this might not work exactly how you'd like, and I appreciate the feedback, I'll be sure to share it with my team.
Sure thing. I think the best feedback from me would be a feature request to allow interoperability between the Page client and server-side components. You can read more about how Next.js routes and hydrates JS via SSR but runs them on the client here. The main point being that there is typically no distinction between a "client" and "server" component in Pages except via API routes. The same Page object should be capable of being statically generated, pre-rendered server side (and hydrated on the client), or rendered entirely client-side with no change in user code. Obviously the static generation case is a little less valuable here, but SSR and CSR should be feasible. At the end of the day, users of Next.js are writing React apps and should be handling Auth0's Page integrations like any React component.
As a sidenote, I'd be more than happy to help out with PRs if this project is accepting them. We are fairly invested in making Next.js work for us, and since we're using Auth0, having this system function properly is fairly important. I have a few proof of concept approaches that lower the barrier to this hybrid approach and would be happy to share.
Appreciate reading this conversation.
Between the two options for retrieving an access token in browser/client-side code:
getAccessTokenSilently
from @auth0/auth0-react
library (i.e. "the SPA workflow")pages/api/accessToken
endpoint which returns a token generated by the getAccessToken
method from @auth0/nextjs-auth0
libraryI understand that both of these carry an elevated risk of exposing the access token through XSS, when compared to the standard Regular Web App model @auth0/nextjs-auth0
which keeps the access token only in server-side code. I understand this risk can be mitigated somewhat by implementing shorter expiration for the tokens.
Does #2 above, "giving Regular Web Access tokens to the frontend" - carry any other elevated risk over #1?
@stevenlybeck
Does #2 above, "giving Regular Web Access tokens to the frontend" - carry any other elevated risk over #1?
No, not really. We have different lifetimes for access tokens issued by Implicit vs Code flows
(from the API settings in Auth0 dashboard)
But a SPA using PKCE would get the longer lifetime anyway
Loving this sdk and it meets our needs pretty closely!
We're hoping to have two access paths to our API endpoint:
- Server-side rendering path - API requests go [nextjs on server] -> [api server]
- Client-side rendering path - API requests go directly [nextjs in browser] -> [api server]
For the client-side rendering path - is there anything wrong with exposing a nextjs API (/pages/api/accessToken) that just returns the value of
await getAccessToken()
? The idea is to make the access token available to the browser so it can make requests directly to our API server.Why don't we want to use the Auth0/React SDK for single page apps? Because we want to support server-side rendering where the data is loaded by the server side of nextjs.
Why don't we want to write an API wrapper so requests are proxied from [nextjs]/api/endpoint to our actual API server? So we don't push all data access unnecessarily through nextjs and require lock-step scaling between nextjs and our API endpoint servers.
Is this a feasible approach? Am I headed down a dark path? Thanks y'all!
Hi @stevenlybeck I am trying to accomplish the same thing but I can seem to do so, any pointers please.
@lsegal +1 to your perspective on this topic, may I ask what approach did you finally take to workaround this?
@adamjmcgrath May I also ask if there's any updates for this since the above discussions last year?
Thank you!
@williamwjs - the draft proposal has expired (https://www.ietf.org/archive/id/draft-bertocci-oauth2-tmi-bff-01.html) and I don't know the status of it. We have no plans to implement this at the moment.
@williamwjs unfortunately a lot had changed for us over the last year and we chose to move away from Next.js in large part due to lack of direct support for this hybrid access model. We're still following along to see if the project starts better supporting this use case.
That said, we did work around this in our PoCs by simply vending our own token from the server routes. Basically we passed the JWT down ourselves and did not use the getAccessToken method. It's been a while so I no longer have the exact details, but hopefully that can help you.
We have kind of the same use case. we use next.js with this library + another service running on express.
I wonder why it is not possible to use the same cookie on both express and next.js. We use the following package: https://github.com/auth0/express-openid-connect on express but it expects cookies to have:
access_token_scope: '',
access_token_expires_at: '',
refresh_token: ''
id_token: ''
But it is in the following format:
accessTokenScope: '',
accessTokenExpiresAt: '',
refreshToken: ''
idToken: ''
Is there something I'm missing ?
next-auth has a slimilar solution in client with react hook: https://next-auth.js.org/getting-started/client#usesession
if auth0-nextjs does't provider it, maybe we also create page//api/session.ts
and wrap request it with react hook.
Dear all, what type of token is delivered by getAccessToken? I use the PoC from the docs "const { accessToken } = await getAccessToken" what delivers some kind of token, but it seems different to what i expected. When i use that token to an API that verifies it as JWT (PemCert based verify Auth0) it states "{"name":"JsonWebTokenError","message":"jwt malformed"}". Thanks.
Solved, found it by setup Audience and Scope in Auth Handler. See: https://github.com/auth0/nextjs-auth0/blob/main/EXAMPLES.md#access-an-external-api-from-an-api-route Scope and Audience can be found in Auth0 Dashboard.
@adamjmcgrath do I get it right that if we want to use authenticated API calls to our own dedicated express server we need to use nextjs api server as a middleware and it is the only secure way?
@JeremieDoctrine Could you please share if you don't mind how you authenticate express app with nextjs cookies? The only way to authenticate another backend I see in the docs is via accessToken header. Maybe I am missing something
Do we have a consensus on how to get the access_token from the client side to issue secured API requests without using a nextjs api middleware and then doing everything with auth0-nextjs server side?
Not sure if this will help anyone, but this is an example of what I've done (using the app router).
// app/api/[...proxy]/route.ts
import { getAccessToken } from "@auth0/nextjs-auth0";
import { NextRequest, NextResponse } from "next/server";
export async function GET(req: NextRequest) {
const res = new NextResponse();
const { accessToken } = await getAccessToken(req, res);
const path = req.nextUrl.pathname.replace("/api/", "");
const EXTERNAL_API_URL = "http://localhost:3000";
return await fetch(`https:/${EXTERNAL_API_URL}/${path}${req.nextUrl.search}`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
}
Then just make a fetch request from the client or server component and it will route all /api/ requests to your external api including your access token.
Not sure if this will help anyone, but this is an example of what I've done (using the app router).
// app/api/[...proxy]/route.ts import { getAccessToken } from "@auth0/nextjs-auth0"; import { NextRequest, NextResponse } from "next/server"; export async function GET(req: NextRequest) { const res = new NextResponse(); const { accessToken } = await getAccessToken(req, res); const path = req.nextUrl.pathname.replace("/api/", ""); const EXTERNAL_API_URL = "http://localhost:3000"; return await fetch(`https:/${EXTERNAL_API_URL}/${path}${req.nextUrl.search}`, { headers: { Authorization: `Bearer ${accessToken}`, }, }); }
Then just make a fetch request from the client or server component and it will route all /api/ requests to your external api including your access token.
could you share the entire code for all other method (GET, POST, PUT, PATCH, DELETE ...)?
@ngothiensinh If that helps, this is our code, which covers more use-cases (and which may certainly be improved):
const withApiProxy = withApiAuthRequired(async (request) => await apiProxy(request, "/api"));
export const GET = withApiProxy;
export const PUT = withApiProxy;
async function apiProxy(request: NextRequest, proxyPath: string): Promise<Response> {
let accessToken;
try {
const accessTokenResult = await getAccessToken(
request,
new NextResponse(),
);
accessToken = accessTokenResult.accessToken;
} catch (e: unknown) {
if (e instanceof AccessTokenError) {
return NextResponse.json(
{ code: e.code, message: e.message },
{ status: e.status ?? 401 },
);
}
throw e;
}
const headers = new Headers(request.headers);
if (accessToken) {
headers.set("authorization", `Bearer ${accessToken}`);
}
return fetch(
`${API_URL}${request.nextUrl.pathname.replace(proxyPath, "")}${request.nextUrl.search}`,
{
...request,
body: request.body && (await request.blob()),
headers,
method: request.method, // I don't know why this needs to be overridden
},
);
}
@mickaeltr, I noticed an issue discussed in the following conversation regarding the use of getAccessToken
and the inability to refresh the token. Did you encounter the same problem in your implementation?
https://community.auth0.com/t/next-js-13-how-to-access-an-external-api-from-an-api-route-using-app-router-api-route/116459
@mickaeltr, I noticed an issue discussed in the following conversation regarding the use of
getAccessToken
and the inability to refresh the token. Did you encounter the same problem in your implementation? https://community.auth0.com/t/next-js-13-how-to-access-an-external-api-from-an-api-route-using-app-router-api-route/116459
I'm not 100% sure, but we likely have the same problem @hbehnam; for now, in case of 401, we're applying a rough solution by logging out the user. 😳
Loving this sdk and it meets our needs pretty closely!
We're hoping to have two access paths to our API endpoint:
For the client-side rendering path - is there anything wrong with exposing a nextjs API (/pages/api/accessToken) that just returns the value of
await getAccessToken()
? The idea is to make the access token available to the browser so it can make requests directly to our API server.Why don't we want to use the Auth0/React SDK for single page apps? Because we want to support server-side rendering where the data is loaded by the server side of nextjs.
Why don't we want to write an API wrapper so requests are proxied from [nextjs]/api/endpoint to our actual API server? So we don't push all data access unnecessarily through nextjs and require lock-step scaling between nextjs and our API endpoint servers.
Is this a feasible approach? Am I headed down a dark path? Thanks y'all!