A reminder that tells a customer their appointment is in 24 hours but requires them to log in to reschedule or cancel is functionally friction. Customers who cannot easily cancel end up as no-shows; hosts who cannot fill that slot lose revenue on both ends. OWASP A07 (Identification & Authentication Failures) applies in reverse: requiring authentication for a low-risk action (cancellation by the verified customer) increases abandonment and no-show rates rather than providing meaningful security. CWE-287 flags the correct design: a time-limited, scoped token grants the specific action to the specific customer without exposing the full account. Tokens that expire at or before appointment time are self-limiting.
Medium because login-gated action links in reminders block customer self-service at the point of highest intent, converting potential self-serve cancellations into no-shows and inbound support contacts.
Generate two time-limited tokens (expiring at appointment time) in src/lib/reminders.ts and embed them in the reminder body. The token handler at the target URL must not require a session cookie:
async function sendReminderMessage(booking: Booking) {
const rescheduleToken = generateToken(booking.id, 'reschedule', booking.start_time)
const cancelToken = generateToken(booking.id, 'cancel', booking.start_time)
const body = [
`Your ${booking.service_name} appointment is coming up at ${formatDateTimeWithTimezone(booking.start_time, booking.timezone)}.`,
`Reschedule: https://yoursite.com/bookings/${booking.id}/reschedule?token=${rescheduleToken}`,
`Cancel: https://yoursite.com/bookings/${booking.id}/cancel?token=${cancelToken}`,
].join('\n\n')
await sendReminderViaEmail(booking.customer_email, body)
}
Verify src/app/api/bookings/[id]/cancel/route.ts validates the token and does not redirect to /login when no session is present.
ID: booking-notifications-reminders.reminders.reminder-one-click-link
Severity: medium
What to look for: Find the reminder message template (email and/or SMS). Search for: (1) A reschedule link — a URL with a token parameter that opens the rescheduling interface. (2) A cancel link — a URL with a token parameter that triggers cancellation. (3) Both links must NOT redirect to a login page — verify the token grants access without authentication. Trace the link target to confirm it handles the token. (4) Check if tokens have an expiration (they should expire before or shortly after the appointment time).
Pass criteria: Count all action links found. ALL of the following: (1) Reminder includes at least 1 action link (reschedule or cancel) — quote the link URL pattern. (2) Link uses a token for unauthenticated access — quote the token generation. (3) Token has an expiration time — report the count of links and the expiry duration even on pass. A link to a generic dashboard that requires login does NOT count as pass.
Fail criteria: No action links in reminder (just informational text). Links require login. Token has no expiration (permanent access). Links point to non-existent routes (404).
Skip (N/A) when: Reminders are not implemented (same criteria as reminder-scheduled-job skip).
Detail on fail: Quote the reminder template text. State which links are present/missing and whether they require authentication. Example: "Reminder template at src/emails/reminder.tsx says 'Your appointment is tomorrow at 3:00 PM' with no action links — no reschedule or cancel URL. Customer must visit the site and log in to take action.".
Remediation: Add time-limited action links with tokens:
// src/lib/reminders.ts
async function sendReminderMessage(booking: Booking) {
// Generate time-limited tokens (expire at appointment time)
const rescheduleToken = generateToken(booking.id, 'reschedule', booking.start_time)
const cancelToken = generateToken(booking.id, 'cancel', booking.start_time)
const message = `
Your appointment for ${booking.service_name} is coming up
at ${formatDateTimeWithTimezone(booking.start_time, booking.timezone)}.
Reschedule: https://yoursite.com/bookings/${booking.id}/reschedule?token=${rescheduleToken}
Cancel: https://yoursite.com/bookings/${booking.id}/cancel?token=${cancelToken}
`
await sendReminderViaEmail(booking.customer_email, message)
}
Cross-reference: Security Headers audit checks token-based URL security. Booking Flow & Lifecycle audit checks reschedule and cancel handlers. Authentication audit checks that token access is properly scoped.