How would you handle auth with existing servers? e.g Laravel via session or JWT.
Unanswered
Sander posted this in #help-forum
SanderOP
So normally people who use Next.JS will do their server stuff on the Node.js server. However in the case of already having a pre-exisiting server made in for example laravel with laravel sanctum via either API Tokens or Session based. How would you tackle that situation? In order to have the power of the NextJS server one needs to make the server aware of that users' auth status.
I understand NextAuth exists and The credentials provider should be the way to do this, however sanctum also comes with session based auth for first party apps on the same domain.
I would prefer to use the session based implementation for my admin panels for example. Which via react you'd do it from the client and it just works with something like axios that handles CSRF, cookies and more. But if we want to reduce the JS to the client by using the server components how could we implement that with sessions?
The database would be SQL.
Sanctum uses HttpOnly cookies which are obviously not accessibly via javascript so how would node handle that and kindly pass them on? Is it possible? Is there already a solution out there that exists but I am unaware off? Any other suggestions?
I'd love to hear what solutions you guys can come up with.
Kind regards,
Sander Cokart
I understand NextAuth exists and The credentials provider should be the way to do this, however sanctum also comes with session based auth for first party apps on the same domain.
I would prefer to use the session based implementation for my admin panels for example. Which via react you'd do it from the client and it just works with something like axios that handles CSRF, cookies and more. But if we want to reduce the JS to the client by using the server components how could we implement that with sessions?
The database would be SQL.
Sanctum uses HttpOnly cookies which are obviously not accessibly via javascript so how would node handle that and kindly pass them on? Is it possible? Is there already a solution out there that exists but I am unaware off? Any other suggestions?
I'd love to hear what solutions you guys can come up with.
Kind regards,
Sander Cokart
69 Replies
Toyger
if you already use laravel maybe it will be easier to use it with inertiajs and react?
but still you can use next-auth or lucia-auth for that too
here example https://medium.com/@rahunn3/next-js-13-app-dir-and-laravel-sanctum-elevating-user-authentication-with-nextauth-js-1368829e4a3a
or this https://github.com/laravel/breeze-next
but still you can use next-auth or lucia-auth for that too
here example https://medium.com/@rahunn3/next-js-13-app-dir-and-laravel-sanctum-elevating-user-authentication-with-nextauth-js-1368829e4a3a
or this https://github.com/laravel/breeze-next
SanderOP
ohw it is 100% easier to use, but I am just seeing if it would be possible to make use of next js's caching abilities.
Breeze next is client only.
And that medium article uses JWT not sessions.
But ill assume one cannot have it easy wanting best of both worlds for first party apps.
Breeze next is client only.
And that medium article uses JWT not sessions.
But ill assume one cannot have it easy wanting best of both worlds for first party apps.
Asian black bear
@Sander I think JWT is your only obvious solution. How would you make your node server aware of the secret session data kept by the laravel server?
How are "first party apps" going to run on your not-actually-laravel website?
SanderOP
yeah fair point. And solidifies my assumptions.
I am running through the steps via https://nextjs.org/learn/dashboard-app/adding-authentication
But with this guide the
But with this guide the
AUTH_SECRET cannot be read from .env.Asian black bear
?
It should work in the dev environment, and depending on where you deploy it using
.env is either a bad idea (vercel) or a ??? idea (custom server)SanderOP
auth.ts is being run on the client right now somewhere. cannot access non NEXT_PUBLIC obviously
ohw dumbass
this https://nextjs.org/learn/dashboard-app/adding-authentication#updating-the-login-form
is missing the
is missing the
'use server'; directiveAsian black bear
oh yeah, not being able to find
AUTH_SECRET in client side bundle sounds like a featureSanderOP
I copy pasted so thats why
Asian black bear
I sincerely dislike NextAuth
it is too popuar for its own good so we will never get something better
another option is to just validate the JWT yourself with like
joseif you have the user login on your PHP site and get redirected to your next side token lifetime is going to be an issue. You will need to figure out if laravel offers a "refresh token" or some other way to extend the JWT life long enough to work.
SanderOP
after the login attempt it once again tries to access the secret from the client
Asian black bear
are you using Laravel Passport?
Asian black bear
what is in authConfig?
also, why are you not using passport? it would make your life so easy
SanderOP
import axios from 'axios';
import CredentialsProvider from 'next-auth/providers/credentials';
import type { NextAuthConfig } from 'next-auth';
export const authConfig = {
callbacks: {
authorized({ auth, request: { nextUrl } }) {
const isLoggedIn = !!auth?.user;
const isOnDashboard = nextUrl.pathname.startsWith('/dashboard');
if (isOnDashboard) {
// Redirect unauthenticated users to login page
return isLoggedIn;
} else if (isLoggedIn) {
return Response.redirect(new URL('/dashboard', nextUrl));
}
return true;
},
},
pages: {
signIn: '/login',
},
providers: [
CredentialsProvider({
name: 'Credentials',
credentials: {
username: { label: 'Email', type: 'text' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials) {
// Add logic here to look up the user from the credentials supplied
const response = await axios.post('http://localhost:8000/api/user', credentials);
const user = response.data;
if (user) {
// Any object returned will be saved in `user` property of the JWT
return user;
} else {
// If you return null then an error will be displayed advising the user to check their details.
return null;
// You can also Reject this callback with an Error thus the user will be sent to the error page with the error message as a query parameter
}
},
}),
],
} satisfies NextAuthConfig;Asian black bear
your next app could just oauth against your laravel server
Asian black bear
Pretty sure they mean an SPA running in your Laravel frontend
not an SPA running entirely-somewhere-else
that is actually literally what oauth is for
also, not really what you might expect from the next.js discord, but why not use Laravel Inertia for your React app?
SanderOP
no it can be a react app seperate too
when you use withCredentials from the client
caching
development experience
as long as its same domain it works
reason next is different is because node server is in between the client and the laravel server
Asian black bear
I am not really a Laravel person and it probably shows
one quick Q, why involve Laravel at all?
and if you want it for API routes, why not have it read Next's JWTs?
SanderOP
because i'd like to use laravel, it makes it easier for me to develop api's faster than learning node js and doing it that way, having the abilities of model observers, jobs, easy database interface, dive into the app with laravel tinker, pest for testing is super powerful and its just a very nice ecosystem
lets put it this way.
in a year or 2 for my work we are going to probably remake our web app using nextjs and we use python with flask for the backend and keycloak for authentication. luckily next auth has keycloak support.
But if we didn't have it, how would nextjs / next auth be able to handle our own data. While passwords are "inherently" insecure, I would like to own the data and not force users to have a google account for example.
in a year or 2 for my work we are going to probably remake our web app using nextjs and we use python with flask for the backend and keycloak for authentication. luckily next auth has keycloak support.
But if we didn't have it, how would nextjs / next auth be able to handle our own data. While passwords are "inherently" insecure, I would like to own the data and not force users to have a google account for example.
Asian black bear
@Sander no worries, I am not saying you shouldn't use it. Just trying to understand what you are trying to do 🙂
Asian black bear
Since you are planning to use Laravel as the back end, Passport would really be a good fit. It would work with any next.js oauth library, and that the user is not logged into a Laravel front end simultaneously is not an issue. (ie. no oauth 2 site awkwardness).
In that case your backend can still handle its own authentication/authorization with the stuff Laraval already knows in its own user database
That way next is mostly a thin interface
bonus points if you can stuff enough info into the JWT that your next app can handle routing and show/hide without pinging your back end every time
SanderOP
Yeah im thinking of having access on both server and client. For some reason so I can block rendering when not authenicated and pass down components in a way that the first page load won't just be a header where the user name is loading in and you gotta wait.
Now I'd be able to fetch large amounts of data tables from the client, or cached by nextjs as either page or cached server actions.
Now I'd be able to fetch large amounts of data tables from the client, or cached by nextjs as either page or cached server actions.
And of course not having to handle redirects on the client is pretty neat, server can completely take over on that part.
Preventing flashes, layout shifts and or redirect madness
SanderOP
It's kinda weird tho like weird feeling when using:
Server actions with caching and use client page caching and server component caching and client side fetch requests and caching with react query or swr. So many layers. But i bet that spreading it this way we can use the best tool for the right job. Not forgetting laravel data caching. I feel like this way we reduce server load significantly? I might be mega wrong but there is no ready handbook for this stuff.
Server actions with caching and use client page caching and server component caching and client side fetch requests and caching with react query or swr. So many layers. But i bet that spreading it this way we can use the best tool for the right job. Not forgetting laravel data caching. I feel like this way we reduce server load significantly? I might be mega wrong but there is no ready handbook for this stuff.
SanderOP
If you were able to write better and or faster APIs using another language or framework whose sole purpose is API, file management and database interface. And you want to create an admin panel or consumer web app what would you've done @Asian black bear ?
There is a rule tho, apps run on same domain but seperate repos. In case we want to create a dev.domain.com with API docs for example for 3rd party as well as internal. Or admin.domain.com for whatever reason.
There is a rule tho, apps run on same domain but seperate repos. In case we want to create a dev.domain.com with API docs for example for 3rd party as well as internal. Or admin.domain.com for whatever reason.
Asian black bear
@Sander There is really a lot less caching on dynamic stuff, so the trick is to push it into the edge runtime and have it in the minimum sized RSC components. When you say same domain do you mean like
SUBDOMAIN: www.stuff.com and api.stuff.com
-or-
SUBPATH: www.stuff.com and www.stuff.com/api
SUBDOMAIN: www.stuff.com and api.stuff.com
-or-
SUBPATH: www.stuff.com and www.stuff.com/api
Asian black bear
Personally I would do the first one with api.stuff.com, because hosting is way more versatile as you don't need a frontend server proxying one/both of them. But yeah, I am pretty much telling you to do what I would do. Use oauth to authenticate the client and pass the JWTs to whatever framework is handing the APIs. The API side can verify the JWT, and if it wants more info not in the token it could access a user database. On the subpath strategy the jwt might be put in a cookie and automatically included in client side requests to the API, but obviously you would need to pass them along no matter what for server side requests to the backend api.
For the oauth service there are kind three options, run your own in NextAuth, run your own in Laravel, or use a 3rd party like Clerk, Kinde, Auth0, Stytch, etc...
For run your own, Laravel would be the most strategic for the backend having user data, but also is crappy because you need to find a generic way to use oauth (which might be some awkward crap with NextAuth)
My preference is use a provider because the cost is typically low enough for my projects to make sense.
@Asian black bear <@244576768701235201> There is really a lot less caching on dynamic stuff, so the trick is to push it into the edge runtime and have it in the minimum sized RSC components. When you say same *domain* do you mean like
SUBDOMAIN: www.stuff.com and api.stuff.com
-or-
SUBPATH: www.stuff.com and www.stuff.com/api
SanderOP
yes www.
Ok so I ended up doing this:
In auth.ts I added this:
The login returns a token and user:
So that when we return the user in authorize:
In auth.ts I added this:
declare module 'next-auth' {
interface User {
token?: string;
}
interface Session {
access_token: string;
}
}The login returns a token and user:
return response()->json([
...$request->user()->toArray(),
'token' => $request->user()->createToken('web')->plainTextToken,
]);So that when we return the user in authorize:
async authorize(credentials) {
const parsedCredentials;
if (parsedCredentials.success) {
//
const user = await getUser({ email, password });
if (!user) return null;
return user;
}
return null;
},Then in the auth.config.ts we add callbacks:
We create an auth provider:
Dashboard layout is wrapped with the provider:
and then client can do requests like so:
callbacks: {
async jwt({ token, user, session, profile, account }) {
if (user) {
token.access_token = user.token;
}
return token;
},
async session({ session, token }) {
if (session) {
session.access_token = token.access_token as string;
}
return session;
},
///////We create an auth provider:
const AuthContext = createContext<null | Partial<Session>>(null);
const AuthContextProvider = ({ session, children }: { session: Partial<Session> | null; children: ReactNode }) => {
return <AuthContext.Provider value={session}>{children}</AuthContext.Provider>;
};
const useAuth = () => {
const context = useContext(AuthContext);
if (context === null) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context as Session & {
access_token: string;
};
};
export { AuthContextProvider, useAuth };Dashboard layout is wrapped with the provider:
const session = await auth();
return (
<AuthContextProvider session={session}>
///
</AuthContextProvider>and then client can do requests like so:
const session = useAuth();
const getUser = async () => {
try {
const response = await axios.get('http://localhost:8000/api/user', {
headers: {
Authorization: `Bearer ${session?.access_token}`,
},
});
console.log(response.data);
return response.data;
} catch (error) {
console.error(error);
return null;
}
};And of course the server can do them as well now.
Everything is httponly and we store nothing in localstorage.
handling refreshes is another feat, but sanctum provides an easy way to do that so next to the access_token you can provide a refresh_token in the same way.
SanderOP
So @Asian black bear what do you think? xD