I'm trying to implement Server Side Authentication with Supabase and Sveltekit. I followed the Quickstart Guide and was able to do authentication client-side. (preventDefault on the submit event and do client-side POST request).
But when trying to do the same thing Server-Side , the auth cookie with the token is not created. Here's the logic:
// src/routes/login/+page.server.ts
import type { PostgrestResponse } from '#supabase/supabase-js';
import { supabaseClient } from '$lib/supabaseClient';
import type { Database } from '$lib/types/database.types';
import type { PageLoad } from './$types';
import type { PageServerLoad, Actions } from './$types';
import { redirect } from '#sveltejs/kit';
export const actions: Actions = {
'login-with-password': async ({ request }) => {
const formData = await request.formData();
const email = formData.get('email');
const password = formData.get('password');
console.log(email, password);
const { data, error } = await supabaseClient.auth.signInWithPassword({ email, password });
console.log(data);
if (error) {
return {
status: 500,
body: {
error: error.message
}
};
}
throw redirect(302, '/');
return { success: true };
}
};
data seems to hold the correct response, with token and everything, but that's not persisted as a cookie.
https://stackblitz.com/~/github.com/gkatsanos/client
Related
I'm having trouble figuring out how to request in backend for a credentials provider login.
I created 2 Credential Provider with different ids(credentials and token)
I attempted the following(per https://next-auth.js.org/getting-started/rest-api#post-apiauthsigninprovider)
POST /api/auth/signin/credentials
POST /api/auth/signin/token
But it seems to return 404 Not Found
I also tried utilizing the route used by the default login form
POST /api/auth/callback/credentials
POST /api/auth/callback/token
But also a 404(also included a csrfToken)
Here is a code snippet
import { HOME_PAGE, LOGIN_PAGE } from '#app/constants/routes';
import { backendApi } from '#app/lib/api';
import { getCsrfToken, signIn } from 'next-auth/react';
export default async function handler(req, res) {
try {
const { accessToken} = req.query;
const csrfToken = await getCsrfToken();
await backendApi.post('/auth/signin/token', {
csrfToken,
accessToken,
});
res.redirect(307, HOME_PAGE);
} catch (error) {
res.redirect(307, LOGIN_PAGE);
}
}
This is code in api/auth/[...nextAuth].js
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import User from "#/models/user";
import connectToDb from "#/config/dbConnection";
export default NextAuth({
session: {
strategy: "jwt",
},
providers: [
CredentialsProvider({
async authorize(credentials) {
connectToDb();
const { email, password } = credentials;
// check if email and passwird entered
if (!email || !password) {
throw new Error("please enter email or password");
}
const user = await User.findOne({
email,
// becasue in pre.save I excluded returning password in api response
}).select("+password");
if (!user) {
throw new Error("Invalid email or password");
}
// check if password is correct
// I added comparePassword to userSchema methods in mongoose
const isPasswordMatched = await user.comparePassword(password);
if (!isPasswordMatched) {
throw new Error("please enter email or password");
}
// creating a promise
return Promise.resolve(user);
},
}),
],
callbacks: {
// jwt callback is only called when token is created
jwt: async ({ token, user }) => {
// user is obj that we have received from authorize Promise.resolve(user)
user && (token.user = user);
// not this token has user property
return Promise.resolve(token);
},
// user arg here is actully token that returned from jwt.
session: async ({ session, token }) => {
// session callback is called whenever a session for that particular user is checked
console.log("user in ...next auth api", token);
session.user = token.user;
// since I get error, I return Promise.resolve(session)
return Promise.resolve(session);
},
},
});
then I write a middleware to get the session:
const isAuthenticatedUser = asyncErrors(async (req, res, next) => {
// when user logged in we create the session of user
// getSession gets auth-token from req.headers
const session = await getSession({ req });
// THIS RETURNS NULL
console.log("session", session);
if (!session) {
return next(new ErrorHandler("Login first to access this resource", 401));
}
req.user = session.user;
next();
});
export { isAuthenticatedUser };
I write api/me to check the user
import nc from "next-connect";
import connectToDb from "#/config/dbConnection";
import { currentUserProfile } from "#/controllers/authController";
import { isAuthenticatedUser } from "#/middlewares/auth";
import onError from "#/middlewares/errors";
const handler = nc({ onError });
connectToDb();
handler.use(isAuthenticatedUser).get(currentUserProfile);
export default handler;
When I visit http://localhost:3000/api/me, since session is logged as null, I get not authneticated, even though, in chrome debugging tools' Aplication tab next-auth credentials are present
Since https://next-auth.js.org/getting-started/upgrade-v4
Version 4 makes using the SessionProvider mandatory.
this is _app.js component
import { SessionProvider } from "next-auth/react"
export default function App({
Component,
pageProps: { session, ...pageProps },
}) {
return (
// `session` comes from `getServerSideProps` or `getInitialProps`.
// Avoids flickering/session loading on first load.
<SessionProvider session={session} refetchInterval={5 * 60}>
<Component {...pageProps} />
</SessionProvider>
)
}
This problem is happening due to not specifying e.preventDefault() on login button.
The working code should look like this :-
async function login(e) {
e.preventDefault(); //Add this to your code.
const getLoginStatus = await signIn("credentials", {
redirect: false,
username,
password,
})};
This question already has answers here:
Nextjs build failing because of jsonwebtoken in _middleware.ts
(3 answers)
Closed 7 months ago.
In this moment I authenticate user in each API. I want to add a middleware to do it only once.
So I created a file named _middleware.ts under /pages/api and used the same approach I did for every API.
I have the following code:
_middleware.ts
import { NextApiRequest } from 'next';
import type { NextFetchEvent, NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import { getUserIdOrFail } from './utils/jwt';
export async function middleware(req: NextRequest, ev: NextFetchEvent) {
const authToken = req.headers.get('Authorization');
let userId: string;
try {
userId = await getUserIdOrFail({
headers: { authorization: authToken },
} as NextApiRequest);
} catch (err) {
//return 401
}
// here usereId might contain the userId if correctly authenticated
return NextResponse.next();
}
jwt.ts
import { sign, verify } from 'jsonwebtoken';
import { config } from './config';
import { User } from '../models/user';
import { jwtSchema } from './schemas/jwtSchema';
import { NextApiRequest } from 'next';
export const buildJwt = (user: User) =>
sign({ id: user._id }, config.JWT_KEY, { expiresIn: config.JWT_EXPIRATION });
export const verifyJwt = <T>(jwt: string) => {
try {
return verify(jwt, config.JWT_KEY) as T;
} catch (e) {
return null;
}
};
export async function getUserIdOrFail(req: NextApiRequest) {
const authorizationHeader = req.headers.authorization;
if (!authorizationHeader || !authorizationHeader.startsWith('Bearer ')) {
throw new Error();
}
const jwt = authorizationHeader.replace('Bearer ', '');
const parsed = verifyJwt(jwt);
const { value, error } = jwtSchema.validate(parsed);
if (error) {
throw new Error();
}
return value.id as string;
}
Basically with this code I get the bearer token that server gave to the user when he logged in or registered contained into the Authorization header and verify if it is valid. If it is valid I take the userId and use it to do operations with the DB.
The problem is that it does the stuff if I use it in the api files, but in this file it goes into error (catch block with this error). Is it a jsonwebtoken problem? Should I to do it differently? Or should I leave it in the way it actually is?
The problem with this approach is that the jasonwebtoken library uses Dynamic code evaluation which is not allowed into the file _middleware.ts in next.js. To solve this need to use another library or do it in the api files.
I have an issue sending a JWT token to the server and using it to authorize access in load handlers. I am using Firebase on the client for authentication. When logged in (onAuthStateChanged), I send a POST request with the token to the /api/login endpoint:
export async function post(req) {
const idToken = req.headers['authorization']
try {
const token = await firebase().auth().verifyIdToken(idToken)
req.locals.user = token.uid
} catch (e) {
console.log(e)
return {
status: 500,
body: 'forbidden',
}
}
return {
status: 200,
body: 'ok',
}
}
In hooks.js:
export function getSession(request) {
return {
user: request.locals.user
}
}
export async function handle({ request, resolve }) {
const cookies = cookie.parse(request.headers.cookie || '')
request.locals.user = cookies.user
const response = await resolve(request)
response.headers['set-cookie'] = `user=${request.locals.user || ''}; Path=/; HttpOnly`
return response
}
In load methods:
export async function load({ session }) {
if (!session.user) {
return {
status: 302,
redirect: '/start'
}
}
// ...
}
All of this works fine except that any client-side navigation after a login is rejected because session.user is still undefined. When navigating by typing the URL in the browser, it works correctly and after that the client-side navigation also works.
Any ideas why and what to do?
I have solved this by adding a browser reload on whichever page the user lands on after logging in. The snippet for the reload on the client side handling on a successful response from the login API endpoint looks like this
if (sessionLoginResponse?.status === "success") {
await signOut(auth);
window.history.back();
setTimeout(() => {
window.location.reload();
}, 10);
}
I am trying to build a react app using relay following instructions from the react-relay step by step guide. In the guide the auth token is stored in the env file, and I am trying to retrieve my token from memory which is created when the user logs in and is passed down to all components using the context API. I am not storing it in local storage and have a refresh token to automatically refresh the JWT.
From the tutorial, the relay environment class is not a React component because of which I cannot access the context object.
Is there a way to pass the token from my context to the relay environment class or any middleware implementation to accomplish this.
Any help is greatly appreciated.
import { useContext } from 'react';
import { Environment, Network, RecordSource, Store } from 'relay-runtime';
import axios from "axios";
import { AppConstants } from './app-constants';
import { AuthContext, AuthSteps } from "./context/auth-context";
import { useCookie } from './hooks/useCookie';
interface IGraphQLResponse {
data: any;
errors: any;
}
async function fetchRelay(params: { text: any; name: any; }, variables: any, _cacheConfig: any) {
const authContext = useContext(AuthContext); //Error - cannot access context
const { getCookie } = useCookie(); //Error - cannot access context
axios.interceptors.request.use(
(config) => {
const accessToken = authContext && authContext.state && authContext.state.token;
if(accessToken) config.headers.Authorization = `Bearer ${accessToken}`;
return config;
},
(error) => {
Promise.reject(error);
}
);
axios.interceptors.response.use(
(response) => {
return response;
},
async (error) => {
const originalRequest = error.config;
const refreshToken = getCookie(AppConstants.AUTH_COOKIE_NAME);
if(refreshToken && error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const response = await axios
.post(process.env.REACT_APP_REFRESH_TOKEN_API_URL!, { refreshToken: refreshToken });
if (response.status === 200 && response.data && response.data.accessToken) {
authContext && authContext.dispatch && authContext.dispatch({
payload: {
token: response.data.accessToken
},
type: AuthSteps.SIGN_IN
});
accessToken = response.data.accessToken;
return axios(originalRequest);
}
}
return Promise.reject(error);
}
);
const data: IGraphQLResponse = await axios.post(process.env.REACT_APP_GRAPHQL_URL!, {
query: params.text,
variables
});
if(Array.isArray(data.errors)) {
throw new Error(
`Error fetching GraphQL query '${
params.name
}' with variables '${JSON.stringify(variables)}': ${JSON.stringify(
data.errors,
)}`,
);
}
return data;
}
export default new Environment({
network: Network.create(fetchRelay),
store: new Store(new RecordSource(), {
gcReleaseBufferSize: 10,
}),
});