I am in the process of incorporating an email address verification feature for users registering on my NextJS website with a WordPress installation as a headless CMS. Here's what I plan to do:
- Set a server token with the following value
{id: <user_id_in_WP_user_database_>, token: <token_fetched_from_REST_API_endpoint>}
- Send an email to the user using nodemailer and prompt them to redirect to my (/verified) route, where I can access the cookie I've set. This allows me to authenticate the user (using another WP REST API endpoint) and update their status to authorized in the WP user database.
The issue arises when I try to retrieve the cookie value upon user redirection from the email link (as the function returns undefined). While inspecting through Chrome developer tools, I can confirm that the cookie is present. Additionally, if I manually input my Route handler URL (/API/cookie), I receive the expected result (the cookie value).
Below, you will find my route handler api/cookie
, which handles setting and retrieving cookies based on the HTTP method:
// API route that validates the user's token by storing it in a cookie
import { NextRequest, NextResponse } from 'next/server';
import { cookies } from "next/headers";
// Export function for GET method
export async function GET(req: NextRequest) {
// GET request to fetch the verification cookie
try {
const cookie = cookies().get("verify")?.value;
console.log('Getting cookie: ', cookies().has("verify"));
if (cookie == undefined) {
return new NextResponse(JSON.stringify({error: 'Missing verification cookie.'}), {status: 400});
}
else {
return new NextResponse(cookie, {status: 200});
}
}
catch (error) {
console.error('Error during verification:', error);
return new NextResponse(JSON.stringify({error}), {status: 500});;
}
}
export async function POST(req: NextRequest) {
try {
const body = await req.json();
console.log('POST request body: ', body)
const expires = new Date(Date.now() + 60 * 60 * 1000);
cookies().set("verify", JSON.stringify(body), { expires, httpOnly: true });
console.log('cookie created: ', cookies().has('verify'))
return new NextResponse(body, {status: 200});;
} catch (error) {
console.error('Error:', error);
return new NextResponse(JSON.stringify({error: 'Server error while creating cookie.'}), {status: 500});;
}
}
Displayed below is the /verified
server route, where users land after clicking the email link:
'use server'
import MaxWidthWrapper from "@/components/MaxWidthWrapper";
import { buttonVariants } from "@/components/ui/button";
import Link from "next/link";
import { wp_fetch } from "@/lib/wp-fetch";
import { Url } from "next/dist/shared/lib/router/router";
import { verify } from "@/lib/verify";
export default async function Verified() {
let verified: String;
let buttonText: String;
let buttonRedirect: Url;
async function isVerified () {
const wpVerified = await verify();
if (typeof wpVerified == 'string'){
verified = wpVerified;
buttonText = 'Return to register form';
buttonRedirect = '/register';
return {verified, buttonText, buttonRedirect};
}
else {
const authenticated = await wp_fetch('PUT', `users/${wpVerified.id}?roles=subscriber`, {roles: 'subscriber'});
if(authenticated.id){
verified = 'Your registration to <span className="text-rose-500">NextJStore</span> has been verified.';
buttonText = 'Please return to homepage and login with your account.';
buttonRedirect = '/';
return {verified, buttonText, buttonRedirect};
}
else{
verified = 'Internal server error. Please try again.';
buttonText = 'Return to register form';
buttonRedirect = '/register';
return {verified, buttonText, buttonRedirect};
}
}
}
return (
<>
<MaxWidthWrapper>
<div className="py-20 mx-auto text-center flex flex-col items-center max-w-3xl">
<h1 className="text-3xl font-bold tracking-tight text-gray-700 sm:text-5xl">
{(await isVerified()).verified}
</h1>
<p className="mt-6 text-lg max-w-prose text-muted-foreground">
{(await isVerified()).buttonText}
</p>
<div className="flex flex-col sm:flex-row gap-4 mt-6">
{/* buttonVariants() applies default styles of the button component. With parameters, the styles change */}
<Link href={(await isVerified()).buttonRedirect} className={buttonVariants()}>
Return
</Link>
</div>
</div>
</MaxWidthWrapper>
</>
);
}
Finally, here is the verify.ts
helper function that I invoke within the route without any parameters:
import { sendEmail } from "@/app/api/cookie/mailer";
import SMTPTransport from "nodemailer/lib/smtp-transport";
export async function verify(username?: string, email?: string, password?: string, id?: number) {
if (username && email && password && id){
const wpAppCredentials = {
username: process.env.NEXT_PUBLIC_WP_ADMIN_USERNAME,
password: process.env.NEXT_PUBLIC_WP_REGISTER_APP_PASSWORD,
};
const encryptedWpAppCredentials = btoa(`${wpAppCredentials.username}:${wpAppCredentials.password}`);
const tokenFetch = await fetch(process.env.NEXT_PUBLIC_JWT_BASE + 'token', {
method: 'POST',
body: JSON.stringify({username, password}),
headers: {
'Content-Type': 'application/json',
'Authorization': `Basic ${encryptedWpAppCredentials}`
},
});
const json = await tokenFetch.json()
console.log('Token:', json)
if(tokenFetch.ok){
const token = json.token
const mailer: SMTPTransport.SentMessageInfo = await sendEmail(username, email, 'VERIFY');
const res = await fetch(process.env.NEXT_PUBLIC_DEPLOY_URL + '/api/cookie', {
method: 'POST',
body: `{"token": "${token}", "id": "${id}"}`
})
const cookie = await res.json();
console.log('Created cookie value: ', cookie)
if (res.ok){
return cookie;
}
else{
console.error('Error while creating the cookie.')
return ('Error while creating the cookie')
}
}
else{
console.error('Error while fetching the token from CMS.')
return ('Error while fetching the token from CMS')
}
}
else if (!username && !email && !password && !id){
const res = await fetch(`${process.env.NEXT_PUBLIC_DEPLOY_URL}api/cookie`, {
method: 'GET'
});
const cookie = await res.json();
console.log('cookie: ', cookie);
if (res.ok){
const validated = await fetch(process.env.NEXT_PUBLIC_JWT_BASE + 'token/validate', {
method: 'POST',
headers: {
'Authorization': `Basic ${cookie.token}`
},
});
console.log('validated: ', validated)
if(validated.status == 200){
return cookie;
}
else{
console.error('Error validating the token.')
return ('Error validating the token');
}
}
else{
console.log('Cookie not found.')
return('Please visit your email to validate the submitted address')
}
}
else{
console.error('Missing required parameters.')
return ('Wrong parameters. Please try again.')
}
}