← กลับไปหน้าบทความ
คู่มือ

ตั้งค่า Supabase Auth ถูกทุกขั้นตอนตามคู่มือ แต่ทำไม Session บน Vercel ยังหลุดกลางทาง

ทีมบรรณาธิการ linli12 ส.ค. 04:53อัปเดต 12 ส.ค. 04:53อ่าน 5 นาที
ตั้งค่า Supabase Auth ถูกทุกขั้นตอนตามคู่มือ แต่ทำไม Session บน Vercel ยังหลุดกลางทาง
บทความนี้จัดทำเพื่อความรู้ ชื่อธุรกิจ ตัวเลข หรือบทสนทนาที่ไม่มีแหล่งอ้างอิงกำกับเป็นกรณีตัวอย่างเพื่ออธิบายแนวคิด ดูรายละเอียดที่ นโยบายกองบรรณาธิการ

สรุปสั้น ๆ

Session ของ Supabase Auth บน Next.js ที่ deploy บน Vercel มักหลุดเพราะสาเหตุที่ไม่ใช่โค้ด auth ผิด แต่เป็นเรื่อง cookie configuration ที่ไม่ตรงกับ production domain, middleware ที่ไม่ refresh token ก่อนหมดอายุ หรือ preview deployment ที่มี URL เปลี่ยนทุกครั้งจนทำให้ cookie scope ผิดที่ ต้องตรวจทั้งสามจุดนี้ควบคู่กันเพื่อให้ session เสถียรจริงบน production

ทีมพัฒนาที่ตั้งค่า Supabase Auth ตามเอกสารทุกขั้นตอน สร้าง client ถูกต้อง เขียน middleware ตามตัวอย่าง แล้ว test ผ่านหมดในเครื่อง มักคาดว่าเมื่อ deploy ขึ้น Vercel แล้วทุกอย่างจะทำงานเหมือนเดิม แต่ในทางปฏิบัติหลายทีมกลับเจอปัญหาผู้ใช้ถูก log out กะทันหันโดยไม่มีสาเหตุชัดเจน หรือ session ใช้งานได้ปกติตอนแรกแล้วหลุดหลังจากผ่านไปสักพัก

ปัญหานี้มักไม่ได้มาจากโค้ด auth ที่เขียนผิด แต่มาจากรายละเอียดของสภาพแวดล้อม production ที่ต่างจาก local development เช่น cookie ที่ผูกกับ domain เฉพาะ, HTTPS ที่บังคับ secure flag, และ preview deployment ของ Vercel ที่สร้าง URL ใหม่ทุกครั้งจน cookie scope ไม่ตรงกับที่คาดไว้

บทความนี้จะไล่ทีละจุดที่ทำให้ session หลุดบน production ทั้งที่ setup ตามเอกสารทุกอย่างแล้ว พร้อมวิธีตรวจสอบและแก้ไขที่ใช้ได้จริง ไม่ใช่แค่ 'ลองใหม่แล้วจะหาย'

Session ของ Supabase Auth เก็บและ Refresh ยังไงจริง ๆ

Supabase Auth ใช้ JWT สองชุดคือ access token ที่อายุสั้น (มักหลักชั่วโมง) และ refresh token ที่อายุยาวกว่ามาก access token ใช้แนบไปกับทุก request เพื่อยืนยันตัวตน ส่วน refresh token ใช้แลก access token ใหม่เมื่อของเดิมหมดอายุ โดยไม่ต้องให้ผู้ใช้ล็อกอินใหม่

ใน Next.js ที่มีทั้ง Server Component และ Client Component ทำงานร่วมกัน token ทั้งสองต้องถูกเก็บในรูปแบบที่ทั้งฝั่ง server และ client อ่านได้ตรงกัน ซึ่งคือ cookie ไม่ใช่ localStorage ที่ฝั่ง server เข้าถึงไม่ได้ นี่คือเหตุผลที่ต้องใช้ library เฉพาะสำหรับ server-side rendering แทน client library ตรง ๆ

จุดสำคัญคือ cookie ที่เก็บ token ต้องถูก refresh ก่อนที่ access token จะหมดอายุ ถ้าไม่มีกลไก refresh ที่ทำงานถูกจังหวะ ผู้ใช้จะเจออาการ session หลุดแบบสุ่ม ๆ ขึ้นกับว่าพอดี request นั้นมาถึงตอน token หมดอายุพอดีหรือไม่

บทบาทของ Middleware ในการ Refresh Token ก่อนหมดอายุ

รูปแบบมาตรฐานที่แนะนำคือใช้ Next.js middleware ตรวจสอบและ refresh session ทุกครั้งที่มี request เข้ามา ก่อนที่ request นั้นจะไปถึง Server Component หรือ Route Handler middleware ทำหน้าที่อ่าน token จาก cookie เช็คว่าใกล้หมดอายุหรือยัง ถ้าใกล้หมดจะเรียก refresh แล้วเขียน cookie ชุดใหม่กลับไปก่อนส่ง response ต่อ

ถ้าลืมเขียน middleware นี้ หรือเขียนแต่ไม่ได้ครอบคลุมทุก route ที่ต้องการ auth state ผู้ใช้จะเจอ token ที่หมดอายุแล้วแต่ยังไม่ถูก refresh ทำให้ Server Component คิดว่าผู้ใช้ไม่ได้ล็อกอิน ทั้งที่จริงแค่ token เก่าหมดอายุพอดี ปัญหานี้มักถูกเข้าใจผิดว่าเป็นบั๊กเรื่อง auth logic ทั้งที่จริงเป็นเรื่องจังหวะการ refresh

อีกจุดที่ทีมมักพลาดคือตั้งค่า matcher ของ middleware ให้ครอบคลุมไม่ครบทุก path ที่ต้องใช้ auth state เช่นลืมรวม API Route บางเส้นทางไว้ใน matcher ทำให้ route นั้นได้ token เก่าที่ไม่ถูก refresh ในขณะที่ route อื่นทำงานปกติ ทำให้ปัญหาดูเหมือนเกิดแบบสุ่มไม่มีรูปแบบ

ตอน development บนเครื่อง local ที่รันผ่าน http://localhost cookie มักทำงานได้ง่ายเพราะไม่มีข้อจำกัดเรื่อง secure flag หรือ cross-site มากนัก แต่พอ deploy ขึ้น production ที่รันผ่าน HTTPS จริง cookie ต้องตั้งค่า secure flag ให้ถูกต้อง และถ้าแอปมีการเรียกข้าม subdomain ต้องตั้งค่า domain ของ cookie ให้ครอบคลุมทุก subdomain ที่เกี่ยวข้อง

ปัญหาที่พบบ่อยคือทีมตั้งค่า cookie domain ผูกกับ URL preview deployment ที่ใช้ตอน development แล้วลืมปรับให้ตรงกับ production domain จริงตอน deploy ทำให้ cookie ถูกเขียนแต่ browser ไม่ยอมส่งกลับมาเพราะ domain ไม่ตรง ผลคือผู้ใช้ดูเหมือนล็อกอินสำเร็จตอนแรกแต่ refresh หน้าแล้วหลุดทันที

แนวทางที่ปลอดภัยกว่าคือไม่ hardcode domain ของ cookie ไว้ตรง ๆ ในโค้ด แต่อ่านจาก environment variable ที่ตั้งค่าต่างกันระหว่าง preview และ production บน Vercel เพื่อให้ cookie ทำงานถูกต้องในทุกสภาพแวดล้อมโดยไม่ต้องแก้โค้ดทุกครั้งที่ deploy

Preview Deployment ของ Vercel ทำให้ Cookie Scope ผิดที่ได้ยังไง

จุดเฉพาะของ Vercel ที่ทีมอื่นอาจไม่ได้เจอคือ Preview Deployment สร้าง URL ใหม่ทุกครั้งที่มี pull request ใหม่ (เช่น project-git-branch-hash.vercel.app) ถ้า Supabase Auth ถูกตั้งค่า redirect URL หรือ allowed origin ไว้แค่ URL production เดียว การล็อกอินบน preview deployment อาจ redirect กลับมาผิดที่ หรือถูกปฏิเสธเพราะ origin ไม่ตรงกับที่อนุญาตไว้

ทางแก้คือตั้งค่า allowed redirect URL ใน Supabase Auth settings ให้รองรับ pattern ของ preview deployment ด้วย ไม่ใช่แค่ production URL เพียงอย่างเดียว บาง Supabase project รองรับ wildcard pattern สำหรับ preview URL ซึ่งควรตรวจ documentation ล่าสุดว่ารองรับ pattern แบบไหนก่อนตั้งค่า

อีกวิธีที่หลายทีมใช้คือแยก Supabase project สำหรับ preview/staging ออกจาก production ไปเลย ตามที่กล่าวถึงในบทความ วาง Next.js Supabase Vercel ให้รันโปรดักชันจริง ซึ่งช่วยแก้ปัญหา redirect URL ไปพร้อมกับการแยกข้อมูลทดสอบออกจากข้อมูลจริงด้วย

ตัวอย่างสมมติ: ไล่ Debug Session ที่หลุดเฉพาะบางกลุ่มผู้ใช้

ลองดูเคสตัวอย่างสมมติของทีมหนึ่งที่ได้รับแจ้งจากผู้ใช้ว่า login แล้วหลุดบ่อยผิดปกติ แต่ทีม dev เองทดสอบแล้วไม่เจอปัญหา ทำให้ debug ยากในช่วงแรก (รายละเอียดทั้งหมดเป็นตัวอย่างสมมติเพื่อประกอบการอธิบายขั้นตอนไล่ปัญหา)

หลังเก็บข้อมูลเพิ่มเติมพบว่าผู้ใช้ที่ร้องเรียนส่วนใหญ่เป็นกลุ่มที่เปิดแอปทิ้งไว้นานเกิน 1 ชั่วโมงโดยไม่ได้ interact อะไร ขณะที่ทีม dev เองมักทดสอบแบบ refresh หน้าใหม่บ่อย ๆ ซึ่งทำให้ token ถูก refresh อยู่ตลอดโดยไม่รู้ตัว จุดนี้ทำให้สงสัยว่าปัญหาอยู่ที่กลไก refresh ตอน token ใกล้หมดอายุพอดี

การตรวจ middleware พบว่า matcher ที่ตั้งไว้ไม่ครอบคลุม API Route บางเส้นทางที่ frontend เรียกแบบ background (เช่น polling ข้อมูลแจ้งเตือน) ทำให้ token ของ route เหล่านั้นไม่เคยถูก refresh เมื่อ access token หมดอายุ request ที่เรียกผ่าน route นี้จะได้ error unauthorized ทำให้แอปแสดงผลว่าผู้ใช้หลุด login ทั้งที่ route อื่นยังทำงานปกติ

ทีมแก้ปัญหาด้วยการปรับ matcher ของ middleware ให้ครอบคลุมทุก path ที่ต้องใช้ auth state รวมถึง API Route แบบ background ทั้งหมด หลังแก้ไขจำนวนรายงานปัญหา session หลุดลดลงอย่างชัดเจนในสัปดาห์ถัดมา บทเรียนสำคัญคือปัญหาแบบนี้มักไม่โผล่ตอนทดสอบแบบปกติ เพราะพฤติกรรมการใช้งานของนักพัฒนากับผู้ใช้จริงต่างกัน

Server Action ต้องเช็ค Auth เองซ้ำ ไม่พึ่ง Middleware อย่างเดียว

จุดหนึ่งที่ทีมมักเข้าใจผิดคือคิดว่า middleware ที่ป้องกัน route ไว้แล้วเพียงพอสำหรับทุกการกระทำในแอป แต่ Server Action ของ Next.js สามารถถูกเรียกได้จากหลายทางแม้ผู้ใช้จะไม่ได้อยู่ที่หน้าที่ middleware ป้องกันไว้โดยตรง เช่นถูกเรียกผ่าน form ที่อาจถูก submit ซ้ำหลังจาก session หมดอายุไปแล้วขณะเปิดหน้าค้างไว้

แนวทางที่ปลอดภัยกว่าคือให้ทุก Server Action ที่แก้ไขข้อมูลสำคัญตรวจสอบ session ของตัวเองอีกชั้นก่อนทำงานจริง ไม่ใช่เชื่อว่า middleware กรองมาให้แล้วทั้งหมด เพราะ middleware ทำงานตอน request เข้ามาถึง route เท่านั้น แต่ Server Action อาจถูกเรียกในจังหวะที่ session เปลี่ยนสถานะไปแล้วระหว่างที่ผู้ใช้เปิดหน้าค้างไว้นาน

การตรวจสอบซ้ำนี้ไม่ได้ทำให้ระบบช้าลงอย่างมีนัยสำคัญ เพราะเป็นการอ่านค่าจาก cookie ที่มีอยู่แล้ว ไม่ต้อง query database เพิ่มเติมในกรณีส่วนใหญ่ แต่ช่วยป้องกันเคสที่ session หมดอายุกลางทางแล้ว action ยังหลุดไปทำงานโดยไม่มีการตรวจสอบใด ๆ

Race Condition ตอน Refresh Token พร้อมกันหลาย Request

อาการ session หลุดแบบสุ่มโดยไม่มีรูปแบบชัดเจนบางครั้งมาจาก race condition ระหว่างการ refresh token เมื่อผู้ใช้เปิดหลาย tab พร้อมกัน หรือแอปยิง request หลายตัวพร้อมกันในจังหวะที่ token ใกล้หมดอายุพอดี แต่ละ request อาจพยายาม refresh token พร้อมกัน ทำให้เกิด token คนละชุดที่แข่งกันเขียนทับ cookie

ผลลัพธ์ที่เกิดขึ้นได้คือ request หนึ่งได้ token ใหม่ที่ถูกต้อง แต่อีก request หนึ่งได้ token ที่ถูกเขียนทับไปแล้วจนใช้งานไม่ได้ ทำให้ผู้ใช้เห็นพฤติกรรมไม่สม่ำเสมอ เช่นบาง tab ใช้งานได้ปกติ แต่อีก tab ต้อง login ใหม่ทั้งที่เป็นผู้ใช้คนเดียวกัน

การลดปัญหานี้ทำได้หลายทาง เช่นใช้กลไก lock หรือ debounce ฝั่ง client ไม่ให้ยิง refresh พร้อมกันหลาย request ในเวลาเดียวกัน หรือให้ middleware ฝั่ง server เป็นจุดเดียวที่ตัดสินใจ refresh แทนที่จะให้ client library แต่ละ tab ตัดสินใจแยกกัน ทีมที่เจออาการนี้บ่อยควรตรวจ pattern การเรียก auth client ว่ามีจุดไหนที่เรียกซ้ำซ้อนโดยไม่จำเป็นหรือไม่ ก่อนจะสรุปว่าเป็นบั๊กของ library เอง

ตารางไล่อาการ Session หลุด กับสาเหตุที่เป็นไปได้

เพื่อให้ทีมอื่นไล่ปัญหาได้เร็วขึ้น นี่คือตารางสรุปอาการที่พบบ่อยกับจุดที่ควรตรวจก่อน

อาการที่เจอจุดที่ควรตรวจก่อน
ล็อกอินสำเร็จแต่ refresh หน้าแล้วหลุดทันทีcookie domain/secure flag ไม่ตรงกับ production URL
หลุดเฉพาะบน preview deployment ไม่หลุดบน productionallowed redirect URL ใน Supabase Auth ไม่รองรับ preview URL pattern
หลุดเฉพาะผู้ใช้ที่เปิดแอปทิ้งไว้นานmiddleware ไม่ refresh token ก่อนหมดอายุ หรือ matcher ไม่ครอบคลุมบาง route
หลุดแบบสุ่ม ไม่มีรูปแบบชัดเจนมี race condition ระหว่าง request พร้อมกันหลายตัวที่พยายาม refresh token เวลาเดียวกัน

ขั้นตอนป้องกันก่อนปล่อย Auth Flow เข้า Production

เพื่อลดโอกาสเจอปัญหาแบบข้างต้น ควรทำตามลำดับขั้นตอนนี้ก่อนปล่อย auth flow เข้า production จริง

  1. ตรวจ matcher ของ middleware ให้ครอบคลุมทุก route ที่ต้องใช้ auth state รวมถึง API Route แบบ background
  2. ตั้งค่า cookie domain ผ่าน environment variable แยกตามสภาพแวดล้อม ไม่ hardcode ค่าตายตัวในโค้ด
  3. เพิ่ม allowed redirect URL สำหรับ preview deployment pattern ใน Supabase Auth settings ไม่ใช่แค่ production URL
  4. ทดสอบ flow login แล้วปล่อยทิ้งไว้เกินอายุของ access token เพื่อยืนยันว่า refresh ทำงานจริงโดยไม่ต้อง login ใหม่
  5. ทดสอบบน preview deployment จริงอย่างน้อยหนึ่งรอบก่อน merge เข้า production ไม่ใช่ทดสอบแค่บนเครื่อง local

สรุป

ปัญหา session หลุดบน production ที่ทำตามเอกสารครบทุกขั้นตอนแล้วยังเจอ มักไม่ได้มาจากโค้ด auth logic ผิด แต่มาจากรายละเอียดของสภาพแวดล้อม production ที่ต่างจาก local development เช่น cookie domain, secure flag และ URL ของ preview deployment ที่เปลี่ยนทุกครั้ง

การป้องกันที่ดีที่สุดคือทดสอบ auth flow บนสภาพแวดล้อมที่ใกล้เคียง production จริงตั้งแต่ช่วง review code ไม่ใช่รอให้ผู้ใช้จริงมาเจอปัญหาก่อนแล้วค่อยไล่ debug ย้อนหลัง

  • ตรวจ matcher ของ middleware ให้ครอบคลุมทุก route ที่ใช้ auth state รวมถึง background API
  • ตั้งค่า cookie domain และ secure flag ผ่าน environment variable แยกตามสภาพแวดล้อม
  • เพิ่ม allowed redirect URL สำหรับ preview deployment pattern ใน Supabase Auth settings

คำถามที่พบบ่อย

ทำไม session ทำงานปกติตอน development แต่หลุดตอน deploy จริง

เพราะ local development มักรันผ่าน http://localhost ที่ไม่มีข้อจำกัดเรื่อง secure flag หรือ domain มากเท่า production ที่รันผ่าน HTTPS จริง ควรทดสอบบน preview deployment ที่ใกล้เคียง production ก่อน merge เสมอ

ต้องตั้งค่า cookie เองทั้งหมดไหม หรือ Supabase library จัดการให้

library ฝั่ง server-side rendering ของ Supabase จัดการ cookie ให้ในระดับหนึ่ง แต่ทีมยังต้องตั้งค่า domain, secure flag และ matcher ของ middleware ให้ตรงกับ production environment เอง เพราะเป็นค่าที่ผูกกับโปรเจกต์แต่ละอัน

Preview Deployment ของ Vercel ต้องใช้ Supabase project แยกไหม

แนะนำให้แยก เพื่อความปลอดภัยของข้อมูล production และเพื่อลดความซับซ้อนของการตั้งค่า allowed redirect URL ที่ต้องรองรับ URL ที่เปลี่ยนทุกครั้งของ preview deployment

ทำไม session หลุดเฉพาะบาง route ไม่หลุดทั้งแอป

มักเกิดจาก matcher ของ middleware ไม่ครอบคลุมทุก route ที่ต้องใช้ auth state ทำให้บาง route ได้ token เก่าที่ไม่ถูก refresh ขณะที่ route อื่นทำงานปกติ

จำเป็นต้อง log ทุกครั้งที่ token refresh ไหม

ควรมี log อย่างน้อยในระดับที่ตรวจสอบย้อนหลังได้ว่า refresh สำเร็จหรือ fail เพราะช่วยไล่ปัญหาได้เร็วกว่ามารอผู้ใช้ร้องเรียนแล้วค่อยเริ่มสืบ

มีวิธีทดสอบ session refresh โดยไม่ต้องรอ token หมดอายุจริงไหม

บางทีมปรับอายุ token ให้สั้นลงชั่วคราวเฉพาะใน environment ทดสอบ เพื่อให้เห็นพฤติกรรม refresh เร็วขึ้นโดยไม่ต้องรอเป็นชั่วโมง แล้วค่อยปรับกลับเป็นค่าปกติก่อนใช้งานจริง

ทดลองวัดผลโฆษณาเข้า LINE ฟรี 14 วัน

สมัครแล้วสร้างโปรเจกต์ เชื่อม LINE ติดตั้ง Tracking แล้วเริ่มเห็น Funnel จาก Click ถึง Purchase ได้ทันที ไม่ต้องใช้บัตรเครดิต

เริ่มทดลองใช้ฟรี 14 วัน

บทความที่เกี่ยวข้อง

ตั้ง Webhook LINE บน Vercel แล้ว Event หายไปครึ่งหนึ่ง ต้องแก้ตรงไหนก่อน

ตั้ง Webhook LINE บน Vercel แล้ว Event หายไปครึ่งหนึ่ง ต้องแก้ตรงไหนก่อน

หลายทีมตั้ง LINE Webhook บน Vercel แล้วเจอปัญหา Event มาไม่ครบ บางเคสได้รับข้อความ บางเคสหายเงียบ บทความนี้ไล่ดูสาเหตุที่พบบ่อยและวิธีวางโครง Webhook ให้เสถียรบน Serverless Function
ผู้ใช้ทักเข้ามาวันละหลักพันข้อความ แต่ Bot เริ่มค้างและตอบช้าเป็นบางช่วง เกิดจากอะไร

ผู้ใช้ทักเข้ามาวันละหลักพันข้อความ แต่ Bot เริ่มค้างและตอบช้าเป็นบางช่วง เกิดจากอะไร

ธุรกิจที่ยิงแอดเข้า LINE ทุกวันแต่ไม่รู้ว่าคนที่ทักเข้ามาไปถึงขั้นตอนไหนต่อ มักมีสาเหตุจาก Bot ที่ตอบช้าหรือมีปัญหาระหว่างขั้นตอนสนทนา บทความนี้อธิบายว่า Cloudflare Workers และ Durable Objects ช่วยสร้าง Bot ที่รักษาสถานะการสนทนาได้แม่นยำและเร็วแค่ไหน เหมาะกับงานแบบไหนบ้าง
เปิดใช้งาน Bot ได้ไม่ถึงเดือน ผู้ใช้เริ่มบ่นว่าทักไปแล้วเงียบ ต้องแก้ยังไง

เปิดใช้งาน Bot ได้ไม่ถึงเดือน ผู้ใช้เริ่มบ่นว่าทักไปแล้วเงียบ ต้องแก้ยังไง

LINE Bot ที่เคยตอบไวตอน Traffic น้อย พอผู้ใช้เพิ่มขึ้นกลับเริ่มตอบช้าและมี Error สุ่ม บทความนี้อธิบายว่าการย้ายฐานข้อมูลมาใช้ Supabase Edge Functions และ Postgres ช่วยแก้จุดคอขวดตรงไหนบ้าง และตรงไหนที่ยังต้องออกแบบเพิ่มเอง