Hi,
I’m currently working with two applications for my website: an SPA for users who log in with email/password, and a Machine-to-Machine (M2M) app. Additionally, I’ve set up a custom Single Sign-On (SSO) for users authenticated through SolidEarth as the Identity Provider (IDP).
The SSO integration is mostly complete. In my Next.js API routes, I’m successfully retrieving user data for SSO logins. However, I’m facing a challenge with session creation for these SSO users. My goal is to establish sessions through my M2M app that would be compatible with users signing in via Auth0’s Universal Login page (Classic) as well.
Here’s my current approach:
- Request a client token using the
/oauth/token
endpoint. - Verify if the user exists via the email API endpoint
/api/v2/users-by-email
. - Create the user if they do not exist, using
/api/v2/users
, or update their details if they already exist with/api/v2/users/${id}
. - Generate an Auth0 client token using
/oauth/token
, and attempt to create a session with it.
The session works within the API route, but upon redirecting the user to the client side, the session isn’t preserved, and Auth0 sends the user to the login page.
Is there a way to manually create and sustain a session that aligns with Auth0’s Classic Universal Login experience, but without using the paid Enterprise SSO solution from Auth0? I’m seeking a free alternative to achieve this.
Any advice or direction on documentation for handling this would be greatly appreciated!
Thank you for your help!
Here is the code on Nestjs API route that handles session and all -
import { TokenSet } from "openid-client";
import { NextApiRequest, NextApiResponse } from "next";
import { v4 } from "uuid";
import {
auth0userExist,
createAuth0User,
getAuth0ClientToken,
getAuth0UserToken,
updateAuth0User,
} from "../../../src/shared/auth/actions";
import {
Claims,
Session,
SessionCache,
getSession,
updateSession,
} from "@auth0/nextjs-auth0";
import axios from "axios";
import { IncomingMessage, ServerResponse } from "http";
import {
NodeCookies,
StatefulSession,
StatelessSession,
} from "../../../src/auth0-session";
import { getConfig } from "../../../src/config";
import { createApolloClient } from "../../../src/shared/apollo/client";
import { User } from "@auth0/auth0-react";
import * as gql from "../../../src/shared/auth/auth-queries";
import auth0 from "../../../utils/auth0";
// import { handle8baseUser } from "../auth/callback";
/**
* Route to login.
*
* @param req - Request from next ctx.
* @param res - Response from next ctx.
*/
export default async function login(
req: NextApiRequest,
res: NextApiResponse
): Promise<void> {
let responseOidc: {
family_name: string;
OfficeName: string;
given_name: string;
email: string;
};
const { baseConfig } = getConfig();
const sessionStore = baseConfig.session.store
? new StatefulSession<
IncomingMessage | NextApiRequest,
ServerResponse | NextApiResponse,
Session
>(baseConfig, NodeCookies)
: new StatelessSession<
IncomingMessage | NextApiRequest,
ServerResponse | NextApiResponse,
Session
>(baseConfig, NodeCookies);
const sessionCache = new SessionCache(baseConfig, sessionStore as any);
try {
const result = await axios.get(
"https://miamirealtors.mysolidearth.com/oauth/userinfo",
{
headers: {
Authorization: "Bearer " + req.query.token,
},
}
);
responseOidc = result?.data;
} catch (error: any) {
res.status(400).json(error?.response?.data);
return;
}
const { email, OfficeName, family_name, given_name } = responseOidc;
const sessionR = await auth0.getSession(req, res);
console.log("sessionR :>> ", sessionR);
// const sessionR = await getSession(req, res);
try {
const password = v4();
const clientToken = await getAuth0ClientToken();
if (!clientToken)
return res.status(400).send({ message: "Failed to get client token" });
let userExist = await auth0userExist(clientToken, email);
if (!userExist) {
userExist = await createAuth0User(clientToken, email, password, {
user_metadata: {
name: `${given_name} ${family_name}`,
companyName: OfficeName,
},
});
} else {
userExist = await updateAuth0User(clientToken, userExist.user_id, {
password,
user_metadata: {
name: `${given_name} ${family_name}`,
companyName: OfficeName,
},
});
}
const userToken = await getAuth0UserToken(email, password);
console.log("auth0 userExist :>> ", userExist, userToken);
const session = await sessionCache.fromTokenSet(new TokenSet(userToken));
await sessionCache.create(req, res, session);
console.log(
"sessionCache. getIdToken:>> ",
await sessionCache.getIdToken(req, res)
);
console.log("sessionCache. get:>> ", await sessionCache.get(req, res));
console.log(
"sessionCache. isAuthenticated:>> ",
await sessionCache.isAuthenticated(req, res)
);
// After acquiring `userToken`, set the session cookie
// Set the session cookie using userToken's id_token
// res.setHeader("Set-Cookie", [
// `auth0.is.authenticated=true; Path=/; HttpOnly; Secure; SameSite=Strict`,
// `auth0.user_token=${userToken.id_token}; Path=/; HttpOnly; Secure; SameSite=Strict`,
// ]);
const updatedSession = {
...session,
user: userExist as Claims,
accessToken: userToken?.access_token || userToken?.accessToken,
idToken: userToken?.id_token || userToken?.idToken,
accessTokenExpiresAt: userToken.expires_in,
accessTokenScope: userToken.scope,
};
await auth0.updateSession(req, res, updatedSession);
// await updateSession(req, res, updatedSession);
// console.log("session :>> ", session);
// console.log("sessionR :>> ", sessionR);
// await handle8baseUser(updatedSession, true);
const client = createApolloClient(userToken?.id_token);
console.log("createApolloClient :>> ", client);
let metadata: User = {};
Object.keys(session.user).forEach((key) => {
if (key.includes("user_metadata")) metadata = session.user[key] || {};
});
/**
* Check if user exists in 8base.
*/
let response;
let exist = false;
try {
response = await client.query({
query: gql.CURRENT_USER_QUERY,
});
console.log("CURRENT_USER_QUERY response :>> ", response);
exist = true;
} catch {
/**
* If user doesn't exist, an error will be
* thrown, which then the new user can be
* created using the authResult values.
*/
console.log("creatingUser");
try {
response = await client.mutate({
mutation: gql.USER_SIGN_UP_MUTATION,
variables: {
user: {
email: session.user.email,
name: `${given_name} ${family_name}`,
companyName: OfficeName,
isFromRealtors: true,
},
authProfileId: process.env.AUTH_PROFILE_ID,
},
});
} catch (error) {
console.log("error", JSON.stringify(error));
throw error;
}
}
if (exist) {
await client.mutate({
mutation: gql.USER_UPDATE_MUTATION,
variables: {
data: {
id: response.data.user.id,
isFromRealtors: true,
},
},
});
}
res
.writeHead(302, {
Location: "/",
})
.end();
return;
} catch (error: any) {
console.error(error);
res
.status(error?.status || 500)
.end(JSON.stringify({ error: error.message }));
}
}