[{"data":1,"prerenderedAt":6985},["ShallowReactive",2],{"navigation_docs":3,"-tu-hoc-database-supabase-authentication":420,"-tu-hoc-database-supabase-authentication-surround":6980},[4,153],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":26},"Phỏng vấn .NET","i-lucide-briefcase","\u002Fphong-van","1.phong-van",[10,27,66,111,128],{"title":11,"icon":12,"path":13,"stem":14,"children":15,"page":26},"Tổng quan","i-lucide-compass","\u002Fphong-van\u002Ftong-quan","1.phong-van\u002F1.tong-quan",[16,21],{"title":17,"path":18,"stem":19,"icon":20},"Thị trường HCM","\u002Fphong-van\u002Ftong-quan\u002Fthi-truong","1.phong-van\u002F1.tong-quan\u002F1.thi-truong","i-lucide-trending-up",{"title":22,"path":23,"stem":24,"icon":25},"Lộ trình 7 ngày","\u002Fphong-van\u002Ftong-quan\u002Flo-trinh","1.phong-van\u002F1.tong-quan\u002F2.lo-trinh","i-lucide-calendar-days",false,{"title":22,"icon":25,"path":28,"stem":29,"children":30,"page":26},"\u002Fphong-van\u002Flo-trinh","1.phong-van\u002F2.lo-trinh",[31,36,41,46,51,56,61],{"title":32,"path":33,"stem":34,"icon":35},"Ngày 1 — C# cơ bản","\u002Fphong-van\u002Flo-trinh\u002Fngay-1-csharp-co-ban","1.phong-van\u002F2.lo-trinh\u002F1.ngay-1-csharp-co-ban","i-lucide-1",{"title":37,"path":38,"stem":39,"icon":40},"Ngày 2 — OOP và SOLID","\u002Fphong-van\u002Flo-trinh\u002Fngay-2-oop-solid","1.phong-van\u002F2.lo-trinh\u002F2.ngay-2-oop-solid","i-lucide-2",{"title":42,"path":43,"stem":44,"icon":45},"Ngày 3 — C# nâng cao","\u002Fphong-van\u002Flo-trinh\u002Fngay-3-csharp-nang-cao","1.phong-van\u002F2.lo-trinh\u002F3.ngay-3-csharp-nang-cao","i-lucide-3",{"title":47,"path":48,"stem":49,"icon":50},"Ngày 4 — Database và SQL","\u002Fphong-van\u002Flo-trinh\u002Fngay-4-database-sql","1.phong-van\u002F2.lo-trinh\u002F4.ngay-4-database-sql","i-lucide-4",{"title":52,"path":53,"stem":54,"icon":55},"Ngày 5 — ASP.NET Core và EF Core","\u002Fphong-van\u002Flo-trinh\u002Fngay-5-aspnet-efcore","1.phong-van\u002F2.lo-trinh\u002F5.ngay-5-aspnet-efcore","i-lucide-5",{"title":57,"path":58,"stem":59,"icon":60},"Ngày 6 — DSA coding","\u002Fphong-van\u002Flo-trinh\u002Fngay-6-dsa","1.phong-van\u002F2.lo-trinh\u002F6.ngay-6-dsa","i-lucide-6",{"title":62,"path":63,"stem":64,"icon":65},"Ngày 7 — Mock và HR","\u002Fphong-van\u002Flo-trinh\u002Fngay-7-mock-interview","1.phong-van\u002F2.lo-trinh\u002F7.ngay-7-mock-interview","i-lucide-7",{"title":67,"icon":68,"path":69,"stem":70,"children":71,"page":26},"Ngân hàng câu hỏi","i-lucide-database","\u002Fphong-van\u002Fngan-hang-cau-hoi","1.phong-van\u002F3.ngan-hang-cau-hoi",[72,77,82,87,92,97,102,107],{"title":73,"path":74,"stem":75,"icon":76},"C# (60+ câu)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Fcsharp","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F1.csharp","i-lucide-hash",{"title":78,"path":79,"stem":80,"icon":81},"OOP và SOLID (40+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Foop","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F2.oop","i-lucide-shapes",{"title":83,"path":84,"stem":85,"icon":86},"SQL (40+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Fsql","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F3.sql","i-lucide-database-zap",{"title":88,"path":89,"stem":90,"icon":91},"ASP.NET và EF (50+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Faspnet-ef","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F4.aspnet-ef","i-lucide-server",{"title":93,"path":94,"stem":95,"icon":96},"DSA và Coding (30+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Fdsa-coding","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F5.dsa-coding","i-lucide-binary",{"title":98,"path":99,"stem":100,"icon":101},"HR và Soft skill (25+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Fhr-behavior","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F6.hr-behavior","i-lucide-users",{"title":103,"path":104,"stem":105,"icon":106},"Design Pattern (55+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Fdesign-pattern","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F7.design-pattern","i-lucide-puzzle",{"title":108,"path":109,"stem":110,"icon":91},"ASP.NET MVC và Web API","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Faspnet-mvc-webapi","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F8.aspnet-mvc-webapi",{"title":112,"itle":113,"icon":114,"path":115,"stem":116,"children":117,"page":26},"Tai Lieu","Tài liệu và Cheat sheet","i-lucide-book-marked","\u002Fphong-van\u002Ftai-lieu","1.phong-van\u002F4.tai-lieu",[118,123],{"title":119,"path":120,"stem":121,"icon":122},"Cheat Sheet","\u002Fphong-van\u002Ftai-lieu\u002Fcheatsheet","1.phong-van\u002F4.tai-lieu\u002F1.cheatsheet","i-lucide-clipboard-list",{"title":124,"path":125,"stem":126,"icon":127},"Nguồn tham khảo","\u002Fphong-van\u002Ftai-lieu\u002Fnguon","1.phong-van\u002F4.tai-lieu\u002F2.nguon","i-lucide-link",{"title":129,"icon":130,"path":131,"stem":132,"children":133,"page":26},"Phỏng vấn English","i-lucide-globe","\u002Fphong-van\u002Fenglish-interview","1.phong-van\u002F5.english-interview",[134,138,143,148],{"title":135,"path":136,"stem":137,"icon":130},"Tổng quan English","\u002Fphong-van\u002Fenglish-interview\u002Ftong-quan","1.phong-van\u002F5.english-interview\u002F1.tong-quan",{"title":139,"path":140,"stem":141,"icon":142},"Vocabulary","\u002Fphong-van\u002Fenglish-interview\u002Fvocabulary","1.phong-van\u002F5.english-interview\u002F2.vocabulary","i-lucide-book-a",{"title":144,"path":145,"stem":146,"icon":147},"Technical Q và A (EN)","\u002Fphong-van\u002Fenglish-interview\u002Ftechnical-en","1.phong-van\u002F5.english-interview\u002F3.technical-en","i-lucide-code",{"title":149,"path":150,"stem":151,"icon":152},"Behavioral (EN)","\u002Fphong-van\u002Fenglish-interview\u002Fbehavioral-en","1.phong-van\u002F5.english-interview\u002F4.behavioral-en","i-lucide-message-square-text",{"title":154,"icon":155,"path":156,"stem":157,"children":158},"Tự học","i-lucide-graduation-cap","\u002Ftu-hoc","2.tu-hoc",[159,161,287,310,333,357,384],{"title":154,"path":156,"stem":160,"icon":155},"2.tu-hoc\u002Findex",{"title":162,"icon":68,"path":163,"stem":164,"children":165},"Database","\u002Ftu-hoc\u002Fdatabase","2.tu-hoc\u002F1.database\u002Findex",[166,167,197,222,243,248],{"title":162,"path":163,"stem":164,"icon":68},{"title":168,"icon":91,"path":169,"stem":170,"children":171,"page":26},"SQL Server","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server","2.tu-hoc\u002F1.database\u002F2.sql-server",[172,177,182,187,192],{"title":173,"path":174,"stem":175,"icon":176},"1. Giới thiệu và Cài đặt","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server\u002Fgioi-thieu-cai-dat","2.tu-hoc\u002F1.database\u002F2.sql-server\u002F1.gioi-thieu-cai-dat","i-lucide-play-circle",{"title":178,"path":179,"stem":180,"icon":181},"2. Cú pháp cơ bản","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server\u002Fcu-phap-co-ban","2.tu-hoc\u002F1.database\u002F2.sql-server\u002F2.cu-phap-co-ban","i-lucide-code-2",{"title":183,"path":184,"stem":185,"icon":186},"3. Truy vấn nâng cao","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server\u002Ftruy-van-nang-cao","2.tu-hoc\u002F1.database\u002F2.sql-server\u002F3.truy-van-nang-cao","i-lucide-git-merge",{"title":188,"path":189,"stem":190,"icon":191},"4. Đặc thù và Performance","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server\u002Fdac-thu-performance","2.tu-hoc\u002F1.database\u002F2.sql-server\u002F4.dac-thu-performance","i-lucide-gauge",{"title":193,"path":194,"stem":195,"icon":196},"5. Q và A phỏng vấn","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server\u002Fcau-hoi-phong-van","2.tu-hoc\u002F1.database\u002F2.sql-server\u002F5.cau-hoi-phong-van","i-lucide-message-circle-question",{"title":198,"icon":199,"path":200,"stem":201,"children":202,"page":26},"PostgreSQL","i-simple-icons-postgresql","\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql","2.tu-hoc\u002F1.database\u002F3.postgresql",[203,206,209,212,215,218],{"title":173,"path":204,"stem":205,"icon":176},"\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Fgioi-thieu-cai-dat","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F1.gioi-thieu-cai-dat",{"title":178,"path":207,"stem":208,"icon":181},"\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Fcu-phap-co-ban","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F2.cu-phap-co-ban",{"title":183,"path":210,"stem":211,"icon":186},"\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Ftruy-van-nang-cao","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F3.truy-van-nang-cao",{"title":188,"path":213,"stem":214,"icon":191},"\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Fdac-thu-performance","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F4.dac-thu-performance",{"title":193,"path":216,"stem":217,"icon":196},"\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Fcau-hoi-phong-van","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F5.cau-hoi-phong-van",{"title":219,"path":220,"stem":221,"icon":96},"6. Tích hợp C# và Postgres","\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Ftich-hop-csharp","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F6.tich-hop-csharp",{"title":223,"icon":224,"path":225,"stem":226,"children":227,"page":26},"MySQL","i-simple-icons-mysql","\u002Ftu-hoc\u002Fdatabase\u002Fmysql","2.tu-hoc\u002F1.database\u002F4.mysql",[228,231,234,237,240],{"title":173,"path":229,"stem":230,"icon":176},"\u002Ftu-hoc\u002Fdatabase\u002Fmysql\u002Fgioi-thieu-cai-dat","2.tu-hoc\u002F1.database\u002F4.mysql\u002F1.gioi-thieu-cai-dat",{"title":178,"path":232,"stem":233,"icon":181},"\u002Ftu-hoc\u002Fdatabase\u002Fmysql\u002Fcu-phap-co-ban","2.tu-hoc\u002F1.database\u002F4.mysql\u002F2.cu-phap-co-ban",{"title":183,"path":235,"stem":236,"icon":186},"\u002Ftu-hoc\u002Fdatabase\u002Fmysql\u002Ftruy-van-nang-cao","2.tu-hoc\u002F1.database\u002F4.mysql\u002F3.truy-van-nang-cao",{"title":188,"path":238,"stem":239,"icon":191},"\u002Ftu-hoc\u002Fdatabase\u002Fmysql\u002Fdac-thu-performance","2.tu-hoc\u002F1.database\u002F4.mysql\u002F4.dac-thu-performance",{"title":193,"path":241,"stem":242,"icon":196},"\u002Ftu-hoc\u002Fdatabase\u002Fmysql\u002Fcau-hoi-phong-van","2.tu-hoc\u002F1.database\u002F4.mysql\u002F5.cau-hoi-phong-van",{"title":244,"path":245,"stem":246,"icon":247},"So sánh 3 RDBMS","\u002Ftu-hoc\u002Fdatabase\u002Fso-sanh","2.tu-hoc\u002F1.database\u002F5.so-sanh","i-lucide-columns-3",{"title":249,"path":250,"stem":251,"children":252,"icon":254},"Supabase (BaaS)","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase","2.tu-hoc\u002F1.database\u002F6.supabase\u002Findex",[253,255,259,264,269,274,278,283],{"title":249,"path":250,"stem":251,"icon":254},"i-simple-icons-supabase",{"title":256,"path":257,"stem":258,"icon":68},"1. Database","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Fdatabase","2.tu-hoc\u002F1.database\u002F6.supabase\u002F1.database",{"title":260,"path":261,"stem":262,"icon":263},"2. Authentication","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Fauthentication","2.tu-hoc\u002F1.database\u002F6.supabase\u002F2.authentication","i-lucide-key",{"title":265,"path":266,"stem":267,"icon":268},"3. Storage & Realtime","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Fstorage-realtime","2.tu-hoc\u002F1.database\u002F6.supabase\u002F3.storage-realtime","i-lucide-zap",{"title":270,"path":271,"stem":272,"icon":273},"4. Edge Functions & AI","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Ffunctions-ai","2.tu-hoc\u002F1.database\u002F6.supabase\u002F4.functions-ai","i-lucide-brain",{"title":275,"path":276,"stem":277,"icon":181},"5. API & SDKs","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Fapi-sdk","2.tu-hoc\u002F1.database\u002F6.supabase\u002F5.api-sdk",{"title":279,"path":280,"stem":281,"icon":282},"6. Platform & Deploy","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Fplatform-deploy","2.tu-hoc\u002F1.database\u002F6.supabase\u002F6.platform-deploy","i-lucide-cloud",{"title":284,"path":285,"stem":286,"icon":196},"7. Interview & Q&A","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Finterview","2.tu-hoc\u002F1.database\u002F6.supabase\u002F7.interview",{"title":288,"icon":289,"path":290,"stem":291,"children":292},"TypeScript","i-simple-icons-typescript","\u002Ftu-hoc\u002Ftypescript","2.tu-hoc\u002F2.typescript\u002Findex",[293,294,297,301,306],{"title":288,"path":290,"stem":291,"icon":289},{"title":173,"path":295,"stem":296,"icon":176},"\u002Ftu-hoc\u002Ftypescript\u002Fgioi-thieu-cai-dat","2.tu-hoc\u002F2.typescript\u002F1.gioi-thieu-cai-dat",{"title":298,"path":299,"stem":300,"icon":81},"2. Type System","\u002Ftu-hoc\u002Ftypescript\u002Ftype-system","2.tu-hoc\u002F2.typescript\u002F2.type-system",{"title":302,"path":303,"stem":304,"icon":305},"3. Advanced types","\u002Ftu-hoc\u002Ftypescript\u002Fadvanced-types","2.tu-hoc\u002F2.typescript\u002F3.advanced-types","i-lucide-braces",{"title":307,"path":308,"stem":309,"icon":196},"4. Q và A phỏng vấn","\u002Ftu-hoc\u002Ftypescript\u002Fcau-hoi-phong-van","2.tu-hoc\u002F2.typescript\u002F4.cau-hoi-phong-van",{"title":311,"icon":81,"path":312,"stem":313,"children":314},"OOP","\u002Ftu-hoc\u002Foop","2.tu-hoc\u002F3.oop\u002Findex",[315,316,321,326,330],{"title":311,"path":312,"stem":313,"icon":81},{"title":317,"path":318,"stem":319,"icon":320},"1. Bốn tính chất","\u002Ftu-hoc\u002Foop\u002F4-tinh-chat","2.tu-hoc\u002F3.oop\u002F1.4-tinh-chat","i-lucide-circle-dot",{"title":322,"path":323,"stem":324,"icon":325},"2. SOLID","\u002Ftu-hoc\u002Foop\u002Fsolid","2.tu-hoc\u002F3.oop\u002F2.solid","i-lucide-shield",{"title":327,"path":328,"stem":329,"icon":106},"3. Design Pattern","\u002Ftu-hoc\u002Foop\u002Fdesign-pattern","2.tu-hoc\u002F3.oop\u002F3.design-pattern",{"title":307,"path":331,"stem":332,"icon":196},"\u002Ftu-hoc\u002Foop\u002Fcau-hoi-phong-van","2.tu-hoc\u002F3.oop\u002F4.cau-hoi-phong-van",{"title":334,"icon":305,"path":335,"stem":336,"children":337},"Generic","\u002Ftu-hoc\u002Fgeneric","2.tu-hoc\u002F4.generic\u002Findex",[338,339,344,349,354],{"title":334,"path":335,"stem":336,"icon":305},{"title":340,"path":341,"stem":342,"icon":343},"1. Khái niệm và cú pháp","\u002Ftu-hoc\u002Fgeneric\u002Fkhai-niem","2.tu-hoc\u002F4.generic\u002F1.khai-niem","i-lucide-book",{"title":345,"path":346,"stem":347,"icon":348},"2. Constraint và Variance","\u002Ftu-hoc\u002Fgeneric\u002Fconstraint-variance","2.tu-hoc\u002F4.generic\u002F2.constraint-variance","i-lucide-filter",{"title":350,"path":351,"stem":352,"icon":353},"3. C# vs TS","\u002Ftu-hoc\u002Fgeneric\u002Fso-sanh","2.tu-hoc\u002F4.generic\u002F3.so-sanh","i-lucide-git-compare",{"title":307,"path":355,"stem":356,"icon":196},"\u002Ftu-hoc\u002Fgeneric\u002Fcau-hoi-phong-van","2.tu-hoc\u002F4.generic\u002F4.cau-hoi-phong-van",{"title":358,"icon":359,"path":360,"stem":361,"children":362},"Nuxt 4","i-simple-icons-nuxt","\u002Ftu-hoc\u002Fnuxt-4","2.tu-hoc\u002F5.nuxt-4\u002Findex",[363,364,367,372,376,379],{"title":358,"path":360,"stem":361,"icon":359},{"title":173,"path":365,"stem":366,"icon":176},"\u002Ftu-hoc\u002Fnuxt-4\u002Fgioi-thieu-cai-dat","2.tu-hoc\u002F5.nuxt-4\u002F1.gioi-thieu-cai-dat",{"title":368,"path":369,"stem":370,"icon":371},"2. Routing và Rendering","\u002Ftu-hoc\u002Fnuxt-4\u002Frouting-rendering","2.tu-hoc\u002F5.nuxt-4\u002F2.routing-rendering","i-lucide-route",{"title":373,"path":374,"stem":375,"icon":91},"3. Data và Server","\u002Ftu-hoc\u002Fnuxt-4\u002Fdata-server","2.tu-hoc\u002F5.nuxt-4\u002F3.data-server",{"title":307,"path":377,"stem":378,"icon":196},"\u002Ftu-hoc\u002Fnuxt-4\u002Fcau-hoi-phong-van","2.tu-hoc\u002F5.nuxt-4\u002F4.cau-hoi-phong-van",{"title":380,"path":381,"stem":382,"icon":383},"5. Practical Todo App","\u002Ftu-hoc\u002Fnuxt-4\u002Fpractical-todo-app","2.tu-hoc\u002F5.nuxt-4\u002F5.practical-todo-app","i-lucide-check-square",{"title":385,"path":386,"stem":387,"children":388,"icon":390},"Tailwind CSS","\u002Ftu-hoc\u002Ftailwindcss","2.tu-hoc\u002F6.tailwindcss\u002Findex",[389,391,396,401,406,411,416],{"title":385,"path":386,"stem":387,"icon":390},"i-simple-icons-tailwindcss",{"title":392,"path":393,"stem":394,"icon":395},"1. Layout & Spacing","\u002Ftu-hoc\u002Ftailwindcss\u002Flayout-spacing","2.tu-hoc\u002F6.tailwindcss\u002F1.layout-spacing","i-lucide-layout",{"title":397,"path":398,"stem":399,"icon":400},"2. Typography & Colors","\u002Ftu-hoc\u002Ftailwindcss\u002Ftypography-colors","2.tu-hoc\u002F6.tailwindcss\u002F2.typography-colors","i-lucide-type",{"title":402,"path":403,"stem":404,"icon":405},"3. Interactive & Effects","\u002Ftu-hoc\u002Ftailwindcss\u002Finteractive-effects","2.tu-hoc\u002F6.tailwindcss\u002F3.interactive-effects","i-lucide-sparkles",{"title":407,"path":408,"stem":409,"icon":410},"4. Responsive & Dark Mode","\u002Ftu-hoc\u002Ftailwindcss\u002Fresponsive-darkmode","2.tu-hoc\u002F6.tailwindcss\u002F4.responsive-darkmode","i-lucide-monitor-smartphone",{"title":412,"path":413,"stem":414,"icon":415},"5. Customization & Theme","\u002Ftu-hoc\u002Ftailwindcss\u002Fcustomization","2.tu-hoc\u002F6.tailwindcss\u002F5.customization","i-lucide-settings-2",{"title":417,"path":418,"stem":419,"icon":181},"6. Practical Projects","\u002Ftu-hoc\u002Ftailwindcss\u002Fpractical-projects","2.tu-hoc\u002F6.tailwindcss\u002F6.practical-projects",{"id":421,"title":422,"body":423,"description":6973,"extension":6974,"links":6975,"meta":6976,"navigation":6977,"path":261,"seo":6978,"stem":262,"__hash__":6979},"docs\u002F2.tu-hoc\u002F1.database\u002F6.supabase\u002F2.authentication.md","Supabase Authentication — Email, OAuth, SSO, MFA",{"type":424,"value":425,"toc":6931},"minimark",[426,430,443,448,459,506,524,528,531,852,857,1034,1059,1063,1114,1120,1124,1130,1145,1148,1152,1156,1159,1413,1419,1423,1426,1542,1545,1557,1561,1748,1753,1810,1817,1826,1830,2053,2058,2306,2312,2327,2334,2338,2341,2433,2437,2516,2520,2523,2624,2629,2880,2890,2894,2947,2954,2956,2960,2971,3139,3143,3530,3538,3651,3697,3699,3703,3706,3710,4175,4179,4327,4331,4591,4595,4796,4812,4814,4818,4828,5209,5213,5247,5249,5253,5256,5324,5472,5592,5615,5617,5621,5624,5684,5822,5829,5831,5835,5838,6230,6232,6236,6242,6281,6301,6303,6307,6311,6314,6407,6418,6422,6453,6457,6498,6599,6602,6606,6690,6692,6696,6708,6723,6729,6746,6752,6769,6771,6775,6919,6921,6927],[427,428,11],"h2",{"id":429},"tổng-quan",[431,432,433,434,438,439,442],"p",{},"Supabase Auth cung cấp xác thực và phân quyền tích hợp sẵn. Dùng ",[435,436,437],"strong",{},"JSON Web Tokens (JWT)"," để xác thực, và tích hợp với ",[435,440,441],{},"Row Level Security (RLS)"," để phân quyền ở tầng database.",[444,445,447],"h3",{"id":446},"kiến-trúc-auth-4-tầng","Kiến trúc Auth — 4 tầng",[449,450,455],"pre",{"className":451,"code":453,"language":454},[452],"language-text","[Client SDK] --> [Kong API Gateway] --> [GoTrue Auth Service] --> [PostgreSQL]\n     |                    |                       |                      |\n     |              Rate limiting,          Xác thực user,         Lưu user data\n     |              routing, API key        sinh JWT, gửi email    trong auth schema\n     |              validation\n","text",[456,457,453],"code",{"__ignoreMap":458},"",[460,461,462,473,479,485],"ol",{},[463,464,465,468,469,472],"li",{},[435,466,467],{},"Client layer"," — Supabase client SDKs (",[456,470,471],{},"@supabase\u002Fsupabase-js",", Flutter, Swift, Python, C#, Kotlin) hoặc HTTP request thủ công",[463,474,475,478],{},[435,476,477],{},"Kong API Gateway"," — Shared gateway cho tất cả Supabase products, xử lý rate limiting và routing",[463,480,481,484],{},[435,482,483],{},"Auth service (GoTrue)"," — Server xác thực, fork từ dự án GoTrue của Netlify, sinh JWT và quản lý session",[463,486,487,490,491,494,495,498,499,498,502,505],{},[435,488,489],{},"Postgres database"," — Lưu trữ user data trong ",[456,492,493],{},"auth"," schema (",[456,496,497],{},"auth.users",", ",[456,500,501],{},"auth.identities",[456,503,504],{},"auth.sessions",", ...)",[507,508,511,512,515,516,519,520,523],"callout",{"color":509,"icon":510},"info","i-lucide-info","Auth schema ",[435,513,514],{},"không được expose qua Data API"," vì lý do bảo mật. Bạn có thể kết nối dữ liệu Auth với bảng của mình bằng foreign keys và triggers. Ví dụ: tạo bảng ",[456,517,518],{},"public.profiles"," với ",[456,521,522],{},"id UUID REFERENCES auth.users(id)",".",[444,525,527],{"id":526},"cấu-trúc-jwt-token","Cấu trúc JWT Token",[431,529,530],{},"Supabase Auth sử dụng JSON Web Token (JWT) làm cơ chế xác thực chính. Mỗi access token chứa các claims sau:",[449,532,536],{"className":533,"code":534,"language":535,"meta":458,"style":458},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"sub\": \"d0d4e701-5a5c-4c8d-b8e7-a1b2c3d4e5f6\",\n  \"email\": \"user@example.com\",\n  \"phone\": \"\",\n  \"app_metadata\": {\n    \"provider\": \"email\",\n    \"providers\": [\"email\", \"google\"]\n  },\n  \"user_metadata\": {\n    \"full_name\": \"Nguyen Van A\",\n    \"avatar_url\": \"https:\u002F\u002F...\"\n  },\n  \"role\": \"authenticated\",\n  \"aal\": \"aal1\",\n  \"session_id\": \"abc123-def456-...\",\n  \"iat\": 1715702400,\n  \"exp\": 1715706000\n}\n","json",[456,537,538,547,575,596,613,628,650,684,690,704,725,745,750,771,792,813,831,846],{"__ignoreMap":458},[539,540,543],"span",{"class":541,"line":542},"line",1,[539,544,546],{"class":545},"sMK4o","{\n",[539,548,550,553,557,560,563,566,570,572],{"class":541,"line":549},2,[539,551,552],{"class":545},"  \"",[539,554,556],{"class":555},"spNyl","sub",[539,558,559],{"class":545},"\"",[539,561,562],{"class":545},":",[539,564,565],{"class":545}," \"",[539,567,569],{"class":568},"sfazB","d0d4e701-5a5c-4c8d-b8e7-a1b2c3d4e5f6",[539,571,559],{"class":545},[539,573,574],{"class":545},",\n",[539,576,578,580,583,585,587,589,592,594],{"class":541,"line":577},3,[539,579,552],{"class":545},[539,581,582],{"class":555},"email",[539,584,559],{"class":545},[539,586,562],{"class":545},[539,588,565],{"class":545},[539,590,591],{"class":568},"user@example.com",[539,593,559],{"class":545},[539,595,574],{"class":545},[539,597,599,601,604,606,608,611],{"class":541,"line":598},4,[539,600,552],{"class":545},[539,602,603],{"class":555},"phone",[539,605,559],{"class":545},[539,607,562],{"class":545},[539,609,610],{"class":545}," \"\"",[539,612,574],{"class":545},[539,614,616,618,621,623,625],{"class":541,"line":615},5,[539,617,552],{"class":545},[539,619,620],{"class":555},"app_metadata",[539,622,559],{"class":545},[539,624,562],{"class":545},[539,626,627],{"class":545}," {\n",[539,629,631,634,638,640,642,644,646,648],{"class":541,"line":630},6,[539,632,633],{"class":545},"    \"",[539,635,637],{"class":636},"sBMFI","provider",[539,639,559],{"class":545},[539,641,562],{"class":545},[539,643,565],{"class":545},[539,645,582],{"class":568},[539,647,559],{"class":545},[539,649,574],{"class":545},[539,651,653,655,658,660,662,665,667,669,671,674,676,679,681],{"class":541,"line":652},7,[539,654,633],{"class":545},[539,656,657],{"class":636},"providers",[539,659,559],{"class":545},[539,661,562],{"class":545},[539,663,664],{"class":545}," [",[539,666,559],{"class":545},[539,668,582],{"class":568},[539,670,559],{"class":545},[539,672,673],{"class":545},",",[539,675,565],{"class":545},[539,677,678],{"class":568},"google",[539,680,559],{"class":545},[539,682,683],{"class":545},"]\n",[539,685,687],{"class":541,"line":686},8,[539,688,689],{"class":545},"  },\n",[539,691,693,695,698,700,702],{"class":541,"line":692},9,[539,694,552],{"class":545},[539,696,697],{"class":555},"user_metadata",[539,699,559],{"class":545},[539,701,562],{"class":545},[539,703,627],{"class":545},[539,705,707,709,712,714,716,718,721,723],{"class":541,"line":706},10,[539,708,633],{"class":545},[539,710,711],{"class":636},"full_name",[539,713,559],{"class":545},[539,715,562],{"class":545},[539,717,565],{"class":545},[539,719,720],{"class":568},"Nguyen Van A",[539,722,559],{"class":545},[539,724,574],{"class":545},[539,726,728,730,733,735,737,739,742],{"class":541,"line":727},11,[539,729,633],{"class":545},[539,731,732],{"class":636},"avatar_url",[539,734,559],{"class":545},[539,736,562],{"class":545},[539,738,565],{"class":545},[539,740,741],{"class":568},"https:\u002F\u002F...",[539,743,744],{"class":545},"\"\n",[539,746,748],{"class":541,"line":747},12,[539,749,689],{"class":545},[539,751,753,755,758,760,762,764,767,769],{"class":541,"line":752},13,[539,754,552],{"class":545},[539,756,757],{"class":555},"role",[539,759,559],{"class":545},[539,761,562],{"class":545},[539,763,565],{"class":545},[539,765,766],{"class":568},"authenticated",[539,768,559],{"class":545},[539,770,574],{"class":545},[539,772,774,776,779,781,783,785,788,790],{"class":541,"line":773},14,[539,775,552],{"class":545},[539,777,778],{"class":555},"aal",[539,780,559],{"class":545},[539,782,562],{"class":545},[539,784,565],{"class":545},[539,786,787],{"class":568},"aal1",[539,789,559],{"class":545},[539,791,574],{"class":545},[539,793,795,797,800,802,804,806,809,811],{"class":541,"line":794},15,[539,796,552],{"class":545},[539,798,799],{"class":555},"session_id",[539,801,559],{"class":545},[539,803,562],{"class":545},[539,805,565],{"class":545},[539,807,808],{"class":568},"abc123-def456-...",[539,810,559],{"class":545},[539,812,574],{"class":545},[539,814,816,818,821,823,825,829],{"class":541,"line":815},16,[539,817,552],{"class":545},[539,819,820],{"class":555},"iat",[539,822,559],{"class":545},[539,824,562],{"class":545},[539,826,828],{"class":827},"sbssI"," 1715702400",[539,830,574],{"class":545},[539,832,834,836,839,841,843],{"class":541,"line":833},17,[539,835,552],{"class":545},[539,837,838],{"class":555},"exp",[539,840,559],{"class":545},[539,842,562],{"class":545},[539,844,845],{"class":827}," 1715706000\n",[539,847,849],{"class":541,"line":848},18,[539,850,851],{"class":545},"}\n",[431,853,854],{},[435,855,856],{},"Giải thích từng claim trong JWT:",[858,859,860,876],"table",{},[861,862,863],"thead",{},[864,865,866,870,873],"tr",{},[867,868,869],"th",{},"Claim",[867,871,872],{},"Ý nghĩa",[867,874,875],{},"Ví dụ",[877,878,879,894,907,921,939,953,972,992,1006,1020],"tbody",{},[864,880,881,886,889],{},[882,883,884],"td",{},[456,885,556],{},[882,887,888],{},"User UUID — định danh duy nhất của user",[882,890,891],{},[456,892,893],{},"d0d4e701-...",[864,895,896,900,903],{},[882,897,898],{},[456,899,582],{},[882,901,902],{},"Email của user (nếu dùng email auth)",[882,904,905],{},[456,906,591],{},[864,908,909,913,916],{},[882,910,911],{},[456,912,603],{},[882,914,915],{},"Số điện thoại (nếu dùng phone auth)",[882,917,918],{},[456,919,920],{},"+84123456789",[864,922,923,927,934],{},[882,924,925],{},[456,926,620],{},[882,928,929,930,933],{},"Metadata từ provider, user ",[435,931,932],{},"không thể"," thay đổi",[882,935,936],{},[456,937,938],{},"{\"provider\": \"email\"}",[864,940,941,945,948],{},[882,942,943],{},[456,944,697],{},[882,946,947],{},"Metadata do app hoặc user set, có thể cập nhật",[882,949,950],{},[456,951,952],{},"{\"full_name\": \"Vinh\"}",[864,954,955,959,968],{},[882,956,957],{},[456,958,757],{},[882,960,961,962,964,965],{},"Phân quyền cơ bản: ",[456,963,766],{}," hoặc ",[456,966,967],{},"anon",[882,969,970],{},[456,971,766],{},[864,973,974,978,988],{},[882,975,976],{},[456,977,778],{},[882,979,980,981,983,984,987],{},"Authenticator Assurance Level: ",[456,982,787],{}," (chưa MFA) hoặc ",[456,985,986],{},"aal2"," (đã MFA)",[882,989,990],{},[456,991,986],{},[864,993,994,998,1001],{},[882,995,996],{},[456,997,799],{},[882,999,1000],{},"UUID của session hiện tại",[882,1002,1003],{},[456,1004,1005],{},"abc123-...",[864,1007,1008,1012,1015],{},[882,1009,1010],{},[456,1011,820],{},[882,1013,1014],{},"Issued At — thời điểm token được tạo (Unix timestamp)",[882,1016,1017],{},[456,1018,1019],{},"1715702400",[864,1021,1022,1026,1029],{},[882,1023,1024],{},[456,1025,838],{},[882,1027,1028],{},"Expiration — thời điểm token hết hạn (Unix timestamp)",[882,1030,1031],{},[456,1032,1033],{},"iat + 3600",[507,1035,1038,1041,1042,1045,1046,1049,1050,1052,1053,1049,1056,1058],{"color":1036,"icon":1037},"primary","i-lucide-star",[435,1039,1040],{},"Interview point:"," JWT claims rất quan trọng để hiểu cách Auth tích hợp với RLS. Khi gọi API, JWT được gửi trong ",[456,1043,1044],{},"Authorization: Bearer \u003Ctoken>",". RLS policies dùng ",[456,1047,1048],{},"auth.uid()"," (lấy từ ",[456,1051,556],{}," claim) và ",[456,1054,1055],{},"auth.role()",[456,1057,757],{}," claim) để phân quyền. Custom claims có thể được thêm qua Custom Access Token Hook.",[444,1060,1062],{"id":1061},"token-lifetime-thời-gian-sống-của-token","Token Lifetime — Thời gian sống của token",[858,1064,1065,1081],{},[861,1066,1067],{},[864,1068,1069,1072,1075,1078],{},[867,1070,1071],{},"Token",[867,1073,1074],{},"Thời gian mặc định",[867,1076,1077],{},"Có thể cấu hình?",[867,1079,1080],{},"Mục đích",[877,1082,1083,1099],{},[864,1084,1085,1090,1093,1096],{},[882,1086,1087],{},[435,1088,1089],{},"Access Token (JWT)",[882,1091,1092],{},"3,600 giây (1 giờ)",[882,1094,1095],{},"Có — Dashboard > Authentication > Settings",[882,1097,1098],{},"Xác thực mỗi API request",[864,1100,1101,1106,1109,1111],{},[882,1102,1103],{},[435,1104,1105],{},"Refresh Token",[882,1107,1108],{},"604,800 giây (7 ngày)",[882,1110,1095],{},[882,1112,1113],{},"Lấy access token mới mà không cần login lại",[507,1115,1116,1119],{"color":509,"icon":510},[435,1117,1118],{},"Tại sao access token ngắn (1h) còn refresh token dài (7d)?"," Access token được gửi trong mỗi HTTP request — nếu bị đánh cắp, attacker chỉ có 1 giờ để khai thác. Refresh token chỉ được gửi đến Supabase Auth server khi cần refresh, ít rủi ro hơn. Nếu refresh token bị lộ, bạn có thể revoke nó trên server.",[444,1121,1123],{"id":1122},"refresh-flow-cơ-chế-tự-động-refresh-token","Refresh Flow — Cơ chế tự động refresh token",[449,1125,1128],{"className":1126,"code":1127,"language":454},[452],"User Login thành công\n  --> Server trả về access_token (JWT, hết hạn sau 1h)\n                    + refresh_token (opaque string, hết hạn sau 7d)\n  --> Client SDK lưu cả hai token trong localStorage \u002F cookie\n\nMỗi API request:\n  --> Client gửi access_token trong header Authorization: Bearer \u003Caccess_token>\n\nKhi access_token hết hạn (sau ~1h):\n  --> Client SDK TỰ ĐỘNG gọi POST \u002Fauth\u002Fv1\u002Ftoken?grant_type=refresh_token\n  --> Gửi refresh_token trong body\n  --> Server kiểm tra refresh_token còn hợp lệ\n  --> Server trả về access_token MỚI (JWT mới, 1h mới)\n                    + có thể refresh_token mới (tùy cấu hình)\n  --> Client cập nhật token mới, TIẾP TỤC gọi API\n  --> USER KHÔNG HỀ BIẾT quá trình này xảy ra\n\nKhi refresh_token hết hạn (sau 7d):\n  --> Server từ chối refresh\n  --> Client SDK phát hiện lỗi, throw error\n  --> User phải login lại từ đầu\n",[456,1129,1127],{"__ignoreMap":458},[507,1131,1132,1134,1135,1137,1138,1141,1142,523],{"color":1036,"icon":1037},[435,1133,1040],{}," Refresh flow là cơ chế quan trọng giúp user không phải login liên tục dù access token ngắn. Client SDK (như ",[456,1136,471],{},") tự động xử lý refresh — developer không cần viết logic refresh thủ công. Khi refresh token cũng hết hạn, ",[456,1139,1140],{},"onAuthStateChange"," fire event ",[456,1143,1144],{},"SIGNED_OUT",[1146,1147],"hr",{},[427,1149,1151],{"id":1150},"các-phương-thức-xác-thực","Các phương thức xác thực",[444,1153,1155],{"id":1154},"_1-email-password","1. Email \u002F Password",[431,1157,1158],{},"Phương thức cơ bản nhất — user đăng ký với email và mật khẩu:",[449,1160,1164],{"className":1161,"code":1162,"language":1163,"meta":458,"style":458},"language-javascript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F Sign up\nconst { data, error } = await supabase.auth.signUp({\n  email: 'user@example.com',\n  password: 'secure-password',\n  options: {\n    data: {\n      full_name: 'Nguyen Van A', \u002F\u002F user metadata\n    }\n  }\n})\n\n\u002F\u002F Email verification được gửi tự động nếu enable trong Dashboard\n\u002F\u002F Sau khi verify, user có thể sign in\n\n\u002F\u002F Sign in\nconst { data, error } = await supabase.auth.signInWithPassword({\n  email: 'user@example.com',\n  password: 'secure-password'\n})\n\n\u002F\u002F data.session chứa access_token (JWT) và refresh_token\n\u002F\u002F data.user chứa user info (id, email, metadata)\n","javascript",[456,1165,1166,1172,1217,1235,1251,1260,1269,1287,1292,1297,1304,1310,1315,1320,1324,1329,1362,1376,1389,1396,1401,1407],{"__ignoreMap":458},[539,1167,1168],{"class":541,"line":542},[539,1169,1171],{"class":1170},"sHwdD","\u002F\u002F Sign up\n",[539,1173,1174,1177,1180,1184,1186,1189,1192,1195,1199,1202,1204,1206,1208,1212,1215],{"class":541,"line":549},[539,1175,1176],{"class":555},"const",[539,1178,1179],{"class":545}," {",[539,1181,1183],{"class":1182},"sTEyZ"," data",[539,1185,673],{"class":545},[539,1187,1188],{"class":1182}," error ",[539,1190,1191],{"class":545},"}",[539,1193,1194],{"class":545}," =",[539,1196,1198],{"class":1197},"s7zQu"," await",[539,1200,1201],{"class":1182}," supabase",[539,1203,523],{"class":545},[539,1205,493],{"class":1182},[539,1207,523],{"class":545},[539,1209,1211],{"class":1210},"s2Zo4","signUp",[539,1213,1214],{"class":1182},"(",[539,1216,546],{"class":545},[539,1218,1219,1223,1225,1228,1230,1233],{"class":541,"line":577},[539,1220,1222],{"class":1221},"swJcz","  email",[539,1224,562],{"class":545},[539,1226,1227],{"class":545}," '",[539,1229,591],{"class":568},[539,1231,1232],{"class":545},"'",[539,1234,574],{"class":545},[539,1236,1237,1240,1242,1244,1247,1249],{"class":541,"line":598},[539,1238,1239],{"class":1221},"  password",[539,1241,562],{"class":545},[539,1243,1227],{"class":545},[539,1245,1246],{"class":568},"secure-password",[539,1248,1232],{"class":545},[539,1250,574],{"class":545},[539,1252,1253,1256,1258],{"class":541,"line":615},[539,1254,1255],{"class":1221},"  options",[539,1257,562],{"class":545},[539,1259,627],{"class":545},[539,1261,1262,1265,1267],{"class":541,"line":630},[539,1263,1264],{"class":1221},"    data",[539,1266,562],{"class":545},[539,1268,627],{"class":545},[539,1270,1271,1274,1276,1278,1280,1282,1284],{"class":541,"line":652},[539,1272,1273],{"class":1221},"      full_name",[539,1275,562],{"class":545},[539,1277,1227],{"class":545},[539,1279,720],{"class":568},[539,1281,1232],{"class":545},[539,1283,673],{"class":545},[539,1285,1286],{"class":1170}," \u002F\u002F user metadata\n",[539,1288,1289],{"class":541,"line":686},[539,1290,1291],{"class":545},"    }\n",[539,1293,1294],{"class":541,"line":692},[539,1295,1296],{"class":545},"  }\n",[539,1298,1299,1301],{"class":541,"line":706},[539,1300,1191],{"class":545},[539,1302,1303],{"class":1182},")\n",[539,1305,1306],{"class":541,"line":727},[539,1307,1309],{"emptyLinePlaceholder":1308},true,"\n",[539,1311,1312],{"class":541,"line":747},[539,1313,1314],{"class":1170},"\u002F\u002F Email verification được gửi tự động nếu enable trong Dashboard\n",[539,1316,1317],{"class":541,"line":752},[539,1318,1319],{"class":1170},"\u002F\u002F Sau khi verify, user có thể sign in\n",[539,1321,1322],{"class":541,"line":773},[539,1323,1309],{"emptyLinePlaceholder":1308},[539,1325,1326],{"class":541,"line":794},[539,1327,1328],{"class":1170},"\u002F\u002F Sign in\n",[539,1330,1331,1333,1335,1337,1339,1341,1343,1345,1347,1349,1351,1353,1355,1358,1360],{"class":541,"line":815},[539,1332,1176],{"class":555},[539,1334,1179],{"class":545},[539,1336,1183],{"class":1182},[539,1338,673],{"class":545},[539,1340,1188],{"class":1182},[539,1342,1191],{"class":545},[539,1344,1194],{"class":545},[539,1346,1198],{"class":1197},[539,1348,1201],{"class":1182},[539,1350,523],{"class":545},[539,1352,493],{"class":1182},[539,1354,523],{"class":545},[539,1356,1357],{"class":1210},"signInWithPassword",[539,1359,1214],{"class":1182},[539,1361,546],{"class":545},[539,1363,1364,1366,1368,1370,1372,1374],{"class":541,"line":833},[539,1365,1222],{"class":1221},[539,1367,562],{"class":545},[539,1369,1227],{"class":545},[539,1371,591],{"class":568},[539,1373,1232],{"class":545},[539,1375,574],{"class":545},[539,1377,1378,1380,1382,1384,1386],{"class":541,"line":848},[539,1379,1239],{"class":1221},[539,1381,562],{"class":545},[539,1383,1227],{"class":545},[539,1385,1246],{"class":568},[539,1387,1388],{"class":545},"'\n",[539,1390,1392,1394],{"class":541,"line":1391},19,[539,1393,1191],{"class":545},[539,1395,1303],{"class":1182},[539,1397,1399],{"class":541,"line":1398},20,[539,1400,1309],{"emptyLinePlaceholder":1308},[539,1402,1404],{"class":541,"line":1403},21,[539,1405,1406],{"class":1170},"\u002F\u002F data.session chứa access_token (JWT) và refresh_token\n",[539,1408,1410],{"class":541,"line":1409},22,[539,1411,1412],{"class":1170},"\u002F\u002F data.user chứa user info (id, email, metadata)\n",[507,1414,1415,1418],{"color":509,"icon":510},[435,1416,1417],{},"Email verification:"," Bạn nên enable email verification trong Dashboard (Authentication > Settings). Khi bật, user phải verify email trước khi đăng nhập. Có thể tùy chỉnh email template và redirect URL.",[444,1420,1422],{"id":1421},"_2-magic-link-không-cần-password","2. Magic Link (không cần password)",[431,1424,1425],{},"User nhận link đăng nhập qua email — không cần nhớ mật khẩu:",[449,1427,1429],{"className":1161,"code":1428,"language":1163,"meta":458,"style":458},"const { data, error } = await supabase.auth.signInWithOtp({\n  email: 'user@example.com',\n  options: {\n    emailRedirectTo: 'https:\u002F\u002Fmyapp.com\u002Fdashboard',\n    shouldCreateUser: true, \u002F\u002F Tự động tạo user nếu chưa tồn tại\n  }\n})\n\n\u002F\u002F User nhận email với magic link\n\u002F\u002F Click link sẽ redirect về app kèm session\n",[456,1430,1431,1464,1478,1486,1502,1518,1522,1528,1532,1537],{"__ignoreMap":458},[539,1432,1433,1435,1437,1439,1441,1443,1445,1447,1449,1451,1453,1455,1457,1460,1462],{"class":541,"line":542},[539,1434,1176],{"class":555},[539,1436,1179],{"class":545},[539,1438,1183],{"class":1182},[539,1440,673],{"class":545},[539,1442,1188],{"class":1182},[539,1444,1191],{"class":545},[539,1446,1194],{"class":545},[539,1448,1198],{"class":1197},[539,1450,1201],{"class":1182},[539,1452,523],{"class":545},[539,1454,493],{"class":1182},[539,1456,523],{"class":545},[539,1458,1459],{"class":1210},"signInWithOtp",[539,1461,1214],{"class":1182},[539,1463,546],{"class":545},[539,1465,1466,1468,1470,1472,1474,1476],{"class":541,"line":549},[539,1467,1222],{"class":1221},[539,1469,562],{"class":545},[539,1471,1227],{"class":545},[539,1473,591],{"class":568},[539,1475,1232],{"class":545},[539,1477,574],{"class":545},[539,1479,1480,1482,1484],{"class":541,"line":577},[539,1481,1255],{"class":1221},[539,1483,562],{"class":545},[539,1485,627],{"class":545},[539,1487,1488,1491,1493,1495,1498,1500],{"class":541,"line":598},[539,1489,1490],{"class":1221},"    emailRedirectTo",[539,1492,562],{"class":545},[539,1494,1227],{"class":545},[539,1496,1497],{"class":568},"https:\u002F\u002Fmyapp.com\u002Fdashboard",[539,1499,1232],{"class":545},[539,1501,574],{"class":545},[539,1503,1504,1507,1509,1513,1515],{"class":541,"line":615},[539,1505,1506],{"class":1221},"    shouldCreateUser",[539,1508,562],{"class":545},[539,1510,1512],{"class":1511},"sfNiH"," true",[539,1514,673],{"class":545},[539,1516,1517],{"class":1170}," \u002F\u002F Tự động tạo user nếu chưa tồn tại\n",[539,1519,1520],{"class":541,"line":630},[539,1521,1296],{"class":545},[539,1523,1524,1526],{"class":541,"line":652},[539,1525,1191],{"class":545},[539,1527,1303],{"class":1182},[539,1529,1530],{"class":541,"line":686},[539,1531,1309],{"emptyLinePlaceholder":1308},[539,1533,1534],{"class":541,"line":692},[539,1535,1536],{"class":1170},"\u002F\u002F User nhận email với magic link\n",[539,1538,1539],{"class":541,"line":706},[539,1540,1541],{"class":1170},"\u002F\u002F Click link sẽ redirect về app kèm session\n",[431,1543,1544],{},"Magic link an toàn hơn password vì:",[1546,1547,1548,1551,1554],"ul",{},[463,1549,1550],{},"Không cần user nhớ mật khẩu",[463,1552,1553],{},"Link chỉ dùng một lần và có thời hạn ngắn",[463,1555,1556],{},"Không có risk bị brute-force password",[444,1558,1560],{"id":1559},"_3-phone-login-otp-qua-sms","3. Phone Login (OTP qua SMS)",[449,1562,1564],{"className":1161,"code":1563,"language":1163,"meta":458,"style":458},"const { data, error } = await supabase.auth.signInWithOtp({\n  phone: '+84123456789',\n  options: {\n    channel: 'sms',\n  }\n})\n\n\u002F\u002F User nhận SMS chứa OTP 6 chữ số\n\u002F\u002F Verify OTP:\nconst { data, session, error } = await supabase.auth.verifyOtp({\n  phone: '+84123456789',\n  token: '123456',\n  type: 'sms'\n})\n",[456,1565,1566,1598,1613,1621,1637,1641,1647,1651,1656,1661,1699,1713,1729,1742],{"__ignoreMap":458},[539,1567,1568,1570,1572,1574,1576,1578,1580,1582,1584,1586,1588,1590,1592,1594,1596],{"class":541,"line":542},[539,1569,1176],{"class":555},[539,1571,1179],{"class":545},[539,1573,1183],{"class":1182},[539,1575,673],{"class":545},[539,1577,1188],{"class":1182},[539,1579,1191],{"class":545},[539,1581,1194],{"class":545},[539,1583,1198],{"class":1197},[539,1585,1201],{"class":1182},[539,1587,523],{"class":545},[539,1589,493],{"class":1182},[539,1591,523],{"class":545},[539,1593,1459],{"class":1210},[539,1595,1214],{"class":1182},[539,1597,546],{"class":545},[539,1599,1600,1603,1605,1607,1609,1611],{"class":541,"line":549},[539,1601,1602],{"class":1221},"  phone",[539,1604,562],{"class":545},[539,1606,1227],{"class":545},[539,1608,920],{"class":568},[539,1610,1232],{"class":545},[539,1612,574],{"class":545},[539,1614,1615,1617,1619],{"class":541,"line":577},[539,1616,1255],{"class":1221},[539,1618,562],{"class":545},[539,1620,627],{"class":545},[539,1622,1623,1626,1628,1630,1633,1635],{"class":541,"line":598},[539,1624,1625],{"class":1221},"    channel",[539,1627,562],{"class":545},[539,1629,1227],{"class":545},[539,1631,1632],{"class":568},"sms",[539,1634,1232],{"class":545},[539,1636,574],{"class":545},[539,1638,1639],{"class":541,"line":615},[539,1640,1296],{"class":545},[539,1642,1643,1645],{"class":541,"line":630},[539,1644,1191],{"class":545},[539,1646,1303],{"class":1182},[539,1648,1649],{"class":541,"line":652},[539,1650,1309],{"emptyLinePlaceholder":1308},[539,1652,1653],{"class":541,"line":686},[539,1654,1655],{"class":1170},"\u002F\u002F User nhận SMS chứa OTP 6 chữ số\n",[539,1657,1658],{"class":541,"line":692},[539,1659,1660],{"class":1170},"\u002F\u002F Verify OTP:\n",[539,1662,1663,1665,1667,1669,1671,1674,1676,1678,1680,1682,1684,1686,1688,1690,1692,1695,1697],{"class":541,"line":706},[539,1664,1176],{"class":555},[539,1666,1179],{"class":545},[539,1668,1183],{"class":1182},[539,1670,673],{"class":545},[539,1672,1673],{"class":1182}," session",[539,1675,673],{"class":545},[539,1677,1188],{"class":1182},[539,1679,1191],{"class":545},[539,1681,1194],{"class":545},[539,1683,1198],{"class":1197},[539,1685,1201],{"class":1182},[539,1687,523],{"class":545},[539,1689,493],{"class":1182},[539,1691,523],{"class":545},[539,1693,1694],{"class":1210},"verifyOtp",[539,1696,1214],{"class":1182},[539,1698,546],{"class":545},[539,1700,1701,1703,1705,1707,1709,1711],{"class":541,"line":727},[539,1702,1602],{"class":1221},[539,1704,562],{"class":545},[539,1706,1227],{"class":545},[539,1708,920],{"class":568},[539,1710,1232],{"class":545},[539,1712,574],{"class":545},[539,1714,1715,1718,1720,1722,1725,1727],{"class":541,"line":747},[539,1716,1717],{"class":1221},"  token",[539,1719,562],{"class":545},[539,1721,1227],{"class":545},[539,1723,1724],{"class":568},"123456",[539,1726,1232],{"class":545},[539,1728,574],{"class":545},[539,1730,1731,1734,1736,1738,1740],{"class":541,"line":752},[539,1732,1733],{"class":1221},"  type",[539,1735,562],{"class":545},[539,1737,1227],{"class":545},[539,1739,1632],{"class":568},[539,1741,1388],{"class":545},[539,1743,1744,1746],{"class":541,"line":773},[539,1745,1191],{"class":545},[539,1747,1303],{"class":1182},[431,1749,1750],{},[435,1751,1752],{},"Phone Auth Providers — chi tiết:",[858,1754,1755,1768],{},[861,1756,1757],{},[864,1758,1759,1762,1765],{},[867,1760,1761],{},"Provider",[867,1763,1764],{},"Đặc điểm",[867,1766,1767],{},"Khu vực hỗ trợ",[877,1769,1770,1783,1796],{},[864,1771,1772,1777,1780],{},[882,1773,1774],{},[435,1775,1776],{},"MessageBird",[882,1778,1779],{},"Global coverage tốt, API đơn giản",[882,1781,1782],{},"Toàn cầu (190+ quốc gia)",[864,1784,1785,1790,1793],{},[882,1786,1787],{},[435,1788,1789],{},"Twilio",[882,1791,1792],{},"Provider phổ biến nhất, nhiều tính năng",[882,1794,1795],{},"Toàn cầu (180+ quốc gia)",[864,1797,1798,1804,1807],{},[882,1799,1800,1803],{},[435,1801,1802],{},"Vonage"," (Nexmo)",[882,1805,1806],{},"Giá cạnh tranh, chất lượng ổn định",[882,1808,1809],{},"Toàn cầu",[431,1811,1812,1813,1816],{},"Có thể cấu hình provider trong Dashboard > Authentication > Providers > Phone. Mỗi provider yêu cầu API key\u002Fsecret riêng. Bạn cũng có thể tạo ",[435,1814,1815],{},"Send SMS Hook"," để dùng provider tùy chỉnh (như AWS SNS, Infobip).",[431,1818,1819,1822,1823,523],{},[435,1820,1821],{},"WhatsApp OTP"," cũng được hỗ trợ thông qua Twilio — cấu hình channel là ",[456,1824,1825],{},"whatsapp",[444,1827,1829],{"id":1828},"_4-social-login-oauth-50-providers","4. Social Login (OAuth) — 50+ providers",[449,1831,1833],{"className":1161,"code":1832,"language":1163,"meta":458,"style":458},"\u002F\u002F Google\nconst { data, error } = await supabase.auth.signInWithOAuth({\n  provider: 'google',\n  options: {\n    redirectTo: 'https:\u002F\u002Fmyapp.com\u002Fauth\u002Fcallback',\n    queryParams: {\n      access_type: 'offline',\n      prompt: 'consent',\n    }\n  }\n})\n\n\u002F\u002F GitHub\nconst { data, error } = await supabase.auth.signInWithOAuth({\n  provider: 'github',\n  options: {\n    redirectTo: 'https:\u002F\u002Fmyapp.com\u002Fauth\u002Fcallback'\n  }\n})\n",[456,1834,1835,1840,1873,1888,1896,1912,1921,1937,1953,1957,1961,1967,1971,1976,2008,2023,2031,2043,2047],{"__ignoreMap":458},[539,1836,1837],{"class":541,"line":542},[539,1838,1839],{"class":1170},"\u002F\u002F Google\n",[539,1841,1842,1844,1846,1848,1850,1852,1854,1856,1858,1860,1862,1864,1866,1869,1871],{"class":541,"line":549},[539,1843,1176],{"class":555},[539,1845,1179],{"class":545},[539,1847,1183],{"class":1182},[539,1849,673],{"class":545},[539,1851,1188],{"class":1182},[539,1853,1191],{"class":545},[539,1855,1194],{"class":545},[539,1857,1198],{"class":1197},[539,1859,1201],{"class":1182},[539,1861,523],{"class":545},[539,1863,493],{"class":1182},[539,1865,523],{"class":545},[539,1867,1868],{"class":1210},"signInWithOAuth",[539,1870,1214],{"class":1182},[539,1872,546],{"class":545},[539,1874,1875,1878,1880,1882,1884,1886],{"class":541,"line":577},[539,1876,1877],{"class":1221},"  provider",[539,1879,562],{"class":545},[539,1881,1227],{"class":545},[539,1883,678],{"class":568},[539,1885,1232],{"class":545},[539,1887,574],{"class":545},[539,1889,1890,1892,1894],{"class":541,"line":598},[539,1891,1255],{"class":1221},[539,1893,562],{"class":545},[539,1895,627],{"class":545},[539,1897,1898,1901,1903,1905,1908,1910],{"class":541,"line":615},[539,1899,1900],{"class":1221},"    redirectTo",[539,1902,562],{"class":545},[539,1904,1227],{"class":545},[539,1906,1907],{"class":568},"https:\u002F\u002Fmyapp.com\u002Fauth\u002Fcallback",[539,1909,1232],{"class":545},[539,1911,574],{"class":545},[539,1913,1914,1917,1919],{"class":541,"line":630},[539,1915,1916],{"class":1221},"    queryParams",[539,1918,562],{"class":545},[539,1920,627],{"class":545},[539,1922,1923,1926,1928,1930,1933,1935],{"class":541,"line":652},[539,1924,1925],{"class":1221},"      access_type",[539,1927,562],{"class":545},[539,1929,1227],{"class":545},[539,1931,1932],{"class":568},"offline",[539,1934,1232],{"class":545},[539,1936,574],{"class":545},[539,1938,1939,1942,1944,1946,1949,1951],{"class":541,"line":686},[539,1940,1941],{"class":1221},"      prompt",[539,1943,562],{"class":545},[539,1945,1227],{"class":545},[539,1947,1948],{"class":568},"consent",[539,1950,1232],{"class":545},[539,1952,574],{"class":545},[539,1954,1955],{"class":541,"line":692},[539,1956,1291],{"class":545},[539,1958,1959],{"class":541,"line":706},[539,1960,1296],{"class":545},[539,1962,1963,1965],{"class":541,"line":727},[539,1964,1191],{"class":545},[539,1966,1303],{"class":1182},[539,1968,1969],{"class":541,"line":747},[539,1970,1309],{"emptyLinePlaceholder":1308},[539,1972,1973],{"class":541,"line":752},[539,1974,1975],{"class":1170},"\u002F\u002F GitHub\n",[539,1977,1978,1980,1982,1984,1986,1988,1990,1992,1994,1996,1998,2000,2002,2004,2006],{"class":541,"line":773},[539,1979,1176],{"class":555},[539,1981,1179],{"class":545},[539,1983,1183],{"class":1182},[539,1985,673],{"class":545},[539,1987,1188],{"class":1182},[539,1989,1191],{"class":545},[539,1991,1194],{"class":545},[539,1993,1198],{"class":1197},[539,1995,1201],{"class":1182},[539,1997,523],{"class":545},[539,1999,493],{"class":1182},[539,2001,523],{"class":545},[539,2003,1868],{"class":1210},[539,2005,1214],{"class":1182},[539,2007,546],{"class":545},[539,2009,2010,2012,2014,2016,2019,2021],{"class":541,"line":794},[539,2011,1877],{"class":1221},[539,2013,562],{"class":545},[539,2015,1227],{"class":545},[539,2017,2018],{"class":568},"github",[539,2020,1232],{"class":545},[539,2022,574],{"class":545},[539,2024,2025,2027,2029],{"class":541,"line":815},[539,2026,1255],{"class":1221},[539,2028,562],{"class":545},[539,2030,627],{"class":545},[539,2032,2033,2035,2037,2039,2041],{"class":541,"line":833},[539,2034,1900],{"class":1221},[539,2036,562],{"class":545},[539,2038,1227],{"class":545},[539,2040,1907],{"class":568},[539,2042,1388],{"class":545},[539,2044,2045],{"class":541,"line":848},[539,2046,1296],{"class":545},[539,2048,2049,2051],{"class":541,"line":1391},[539,2050,1191],{"class":545},[539,2052,1303],{"class":1182},[431,2054,2055],{},[435,2056,2057],{},"Danh sách đầy đủ các OAuth provider được hỗ trợ:",[858,2059,2060,2072],{},[861,2061,2062],{},[864,2063,2064,2066,2069],{},[867,2065,1761],{},[867,2067,2068],{},"Loại",[867,2070,2071],{},"Ghi chú",[877,2073,2074,2087,2100,2113,2125,2137,2149,2161,2173,2185,2197,2209,2221,2234,2246,2258,2270,2282,2294],{},[864,2075,2076,2081,2084],{},[882,2077,2078],{},[435,2079,2080],{},"Apple",[882,2082,2083],{},"Social",[882,2085,2086],{},"Hỗ trợ Sign in with Apple, iOS\u002FmacOS native",[864,2088,2089,2094,2097],{},[882,2090,2091],{},[435,2092,2093],{},"Azure (Microsoft)",[882,2095,2096],{},"Enterprise",[882,2098,2099],{},"Azure AD \u002F Microsoft Entra ID, hỗ trợ multi-tenant",[864,2101,2102,2107,2110],{},[882,2103,2104],{},[435,2105,2106],{},"Bitbucket",[882,2108,2109],{},"Developer",[882,2111,2112],{},"Dành cho team dùng Atlassian ecosystem",[864,2114,2115,2120,2122],{},[882,2116,2117],{},[435,2118,2119],{},"Discord",[882,2121,2083],{},[882,2123,2124],{},"Phổ biến cho game, community apps",[864,2126,2127,2132,2134],{},[882,2128,2129],{},[435,2130,2131],{},"Facebook",[882,2133,2083],{},[882,2135,2136],{},"Cần Facebook App ID + App Secret",[864,2138,2139,2144,2146],{},[882,2140,2141],{},[435,2142,2143],{},"Figma",[882,2145,2109],{},[882,2147,2148],{},"Dành cho design\u002Fdeveloper tools",[864,2150,2151,2156,2158],{},[882,2152,2153],{},[435,2154,2155],{},"GitHub",[882,2157,2109],{},[882,2159,2160],{},"Phổ biến cho developer tools, SaaS",[864,2162,2163,2168,2170],{},[882,2164,2165],{},[435,2166,2167],{},"GitLab",[882,2169,2109],{},[882,2171,2172],{},"Self-managed GitLab instances cũng được hỗ trợ",[864,2174,2175,2180,2182],{},[882,2176,2177],{},[435,2178,2179],{},"Google",[882,2181,2083],{},[882,2183,2184],{},"Provider phổ biến nhất, hỗ trợ Google Workspace domains",[864,2186,2187,2192,2194],{},[882,2188,2189],{},[435,2190,2191],{},"Kakao",[882,2193,2083],{},[882,2195,2196],{},"Phổ biến ở Hàn Quốc (KakaoTalk)",[864,2198,2199,2204,2206],{},[882,2200,2201],{},[435,2202,2203],{},"Keycloak",[882,2205,2096],{},[882,2207,2208],{},"Open-source identity provider, tự host",[864,2210,2211,2216,2218],{},[882,2212,2213],{},[435,2214,2215],{},"LinkedIn",[882,2217,2083],{},[882,2219,2220],{},"Dành cho professional\u002Frecruitment apps",[864,2222,2223,2228,2231],{},[882,2224,2225],{},[435,2226,2227],{},"Notion",[882,2229,2230],{},"Productivity",[882,2232,2233],{},"Dành cho apps tích hợp với Notion",[864,2235,2236,2241,2243],{},[882,2237,2238],{},[435,2239,2240],{},"Slack",[882,2242,2230],{},[882,2244,2245],{},"Dành cho workspace\u002Fbusiness apps",[864,2247,2248,2253,2255],{},[882,2249,2250],{},[435,2251,2252],{},"Spotify",[882,2254,2083],{},[882,2256,2257],{},"Dành cho music\u002Faudio apps",[864,2259,2260,2265,2267],{},[882,2261,2262],{},[435,2263,2264],{},"Twitter (X)",[882,2266,2083],{},[882,2268,2269],{},"Cần Twitter API v2 credentials",[864,2271,2272,2277,2279],{},[882,2273,2274],{},[435,2275,2276],{},"Twitch",[882,2278,2083],{},[882,2280,2281],{},"Dành cho gaming\u002Fstreaming apps",[864,2283,2284,2289,2291],{},[882,2285,2286],{},[435,2287,2288],{},"WorkOS",[882,2290,2096],{},[882,2292,2293],{},"Hỗ trợ nhiều enterprise IdPs qua một integration",[864,2295,2296,2301,2303],{},[882,2297,2298],{},[435,2299,2300],{},"Zoom",[882,2302,2230],{},[882,2304,2305],{},"Dành cho video conferencing apps",[431,2307,2308,2309,523],{},"Các provider khác: ",[435,2310,2311],{},"Auth0, AWS Cognito, Battle.net, Coinbase, Deezer, DigitalOcean, Discord, Dropbox, eBay, Etsy, Facebook (custom), Fitbit, Heroku, HubSpot, Instagram, Intercom, Kakao, Keycloak, Line, Linear, Mailchimp, Meetup, Naver, Notion, Okta, Patreon, Paypal, Pipedrive, Reddit, Salesforce, Shopify, Slack, SoundCloud, Spotify, Strava, Stripe, Tableau, TikTok, Todoist, Typeform, Uber, VK, X (Twitter), Yahoo, Yandex, YouTube, Zendesk, Zoom",[507,2313,2314,2316,2317,2320,2321,2323,2324,2326],{"color":1036,"icon":1037},[435,2315,1040],{}," Mỗi OAuth provider cần Client ID + Client Secret được cấu hình trong Dashboard. Redirect URL tự động là ",[456,2318,2319],{},"https:\u002F\u002F\u003Cproject>.supabase.co\u002Fauth\u002Fv1\u002Fcallback",". Khi user login bằng OAuth, Supabase tự động tạo record trong ",[456,2322,501],{}," liên kết với ",[456,2325,497],{},". Một user có thể có nhiều identities (ví dụ: vừa Google vừa GitHub).",[431,2328,2329,2330,2333],{},"Bạn cũng có thể thêm ",[435,2331,2332],{},"Custom OAuth\u002FOIDC Provider"," — cấu hình Client ID, Client Secret, Authorization URL, Token URL, và User Info URL.",[444,2335,2337],{"id":2336},"_5-enterprise-sso-saml-20","5. Enterprise SSO (SAML 2.0)",[431,2339,2340],{},"Hỗ trợ Single Sign-On cho doanh nghiệp với SAML 2.0. Dùng cho các ứng dụng enterprise với identity provider như Okta, Azure AD, OneLogin.",[449,2342,2344],{"className":1161,"code":2343,"language":1163,"meta":458,"style":458},"\u002F\u002F SSO sign in\nconst { data, error } = await supabase.auth.signInWithSSO({\n  domain: 'company.com', \u002F\u002F Email domain hoặc organization slug\n  options: {\n    redirectTo: 'https:\u002F\u002Fmyapp.com\u002Fdashboard'\n  }\n})\n",[456,2345,2346,2351,2384,2403,2411,2423,2427],{"__ignoreMap":458},[539,2347,2348],{"class":541,"line":542},[539,2349,2350],{"class":1170},"\u002F\u002F SSO sign in\n",[539,2352,2353,2355,2357,2359,2361,2363,2365,2367,2369,2371,2373,2375,2377,2380,2382],{"class":541,"line":549},[539,2354,1176],{"class":555},[539,2356,1179],{"class":545},[539,2358,1183],{"class":1182},[539,2360,673],{"class":545},[539,2362,1188],{"class":1182},[539,2364,1191],{"class":545},[539,2366,1194],{"class":545},[539,2368,1198],{"class":1197},[539,2370,1201],{"class":1182},[539,2372,523],{"class":545},[539,2374,493],{"class":1182},[539,2376,523],{"class":545},[539,2378,2379],{"class":1210},"signInWithSSO",[539,2381,1214],{"class":1182},[539,2383,546],{"class":545},[539,2385,2386,2389,2391,2393,2396,2398,2400],{"class":541,"line":577},[539,2387,2388],{"class":1221},"  domain",[539,2390,562],{"class":545},[539,2392,1227],{"class":545},[539,2394,2395],{"class":568},"company.com",[539,2397,1232],{"class":545},[539,2399,673],{"class":545},[539,2401,2402],{"class":1170}," \u002F\u002F Email domain hoặc organization slug\n",[539,2404,2405,2407,2409],{"class":541,"line":598},[539,2406,1255],{"class":1221},[539,2408,562],{"class":545},[539,2410,627],{"class":545},[539,2412,2413,2415,2417,2419,2421],{"class":541,"line":615},[539,2414,1900],{"class":1221},[539,2416,562],{"class":545},[539,2418,1227],{"class":545},[539,2420,1497],{"class":568},[539,2422,1388],{"class":545},[539,2424,2425],{"class":541,"line":630},[539,2426,1296],{"class":545},[539,2428,2429,2431],{"class":541,"line":652},[539,2430,1191],{"class":545},[539,2432,1303],{"class":1182},[444,2434,2436],{"id":2435},"_6-passkey-webauthn-sinh-trắc-học","6. Passkey (WebAuthn) — Sinh trắc học",[449,2438,2440],{"className":1161,"code":2439,"language":1163,"meta":458,"style":458},"\u002F\u002F Register passkey (Face ID, Touch ID, Windows Hello)\nconst { data } = await supabase.auth.registerPasskey()\n\n\u002F\u002F Sign in với passkey\nconst { data, error } = await supabase.auth.signInWithPasskey()\n",[456,2441,2442,2447,2476,2480,2485],{"__ignoreMap":458},[539,2443,2444],{"class":541,"line":542},[539,2445,2446],{"class":1170},"\u002F\u002F Register passkey (Face ID, Touch ID, Windows Hello)\n",[539,2448,2449,2451,2453,2456,2458,2460,2462,2464,2466,2468,2470,2473],{"class":541,"line":549},[539,2450,1176],{"class":555},[539,2452,1179],{"class":545},[539,2454,2455],{"class":1182}," data ",[539,2457,1191],{"class":545},[539,2459,1194],{"class":545},[539,2461,1198],{"class":1197},[539,2463,1201],{"class":1182},[539,2465,523],{"class":545},[539,2467,493],{"class":1182},[539,2469,523],{"class":545},[539,2471,2472],{"class":1210},"registerPasskey",[539,2474,2475],{"class":1182},"()\n",[539,2477,2478],{"class":541,"line":577},[539,2479,1309],{"emptyLinePlaceholder":1308},[539,2481,2482],{"class":541,"line":598},[539,2483,2484],{"class":1170},"\u002F\u002F Sign in với passkey\n",[539,2486,2487,2489,2491,2493,2495,2497,2499,2501,2503,2505,2507,2509,2511,2514],{"class":541,"line":615},[539,2488,1176],{"class":555},[539,2490,1179],{"class":545},[539,2492,1183],{"class":1182},[539,2494,673],{"class":545},[539,2496,1188],{"class":1182},[539,2498,1191],{"class":545},[539,2500,1194],{"class":545},[539,2502,1198],{"class":1197},[539,2504,1201],{"class":1182},[539,2506,523],{"class":545},[539,2508,493],{"class":1182},[539,2510,523],{"class":545},[539,2512,2513],{"class":1210},"signInWithPasskey",[539,2515,2475],{"class":1182},[444,2517,2519],{"id":2518},"_7-anonymous-sign-ins","7. Anonymous Sign-Ins",[431,2521,2522],{},"Cho phép người dùng dùng app mà không cần đăng ký, sau đó có thể liên kết tài khoản:",[449,2524,2526],{"className":1161,"code":2525,"language":1163,"meta":458,"style":458},"\u002F\u002F Anonymous sign in\nconst { data, error } = await supabase.auth.signInAnonymously()\n\n\u002F\u002F Sau đó, user có thể link với email\u002FOAuth để trở thành permanent user\nconst { data, error } = await supabase.auth.linkIdentity({\n  provider: 'google'\n})\n",[456,2527,2528,2533,2564,2568,2573,2606,2618],{"__ignoreMap":458},[539,2529,2530],{"class":541,"line":542},[539,2531,2532],{"class":1170},"\u002F\u002F Anonymous sign in\n",[539,2534,2535,2537,2539,2541,2543,2545,2547,2549,2551,2553,2555,2557,2559,2562],{"class":541,"line":549},[539,2536,1176],{"class":555},[539,2538,1179],{"class":545},[539,2540,1183],{"class":1182},[539,2542,673],{"class":545},[539,2544,1188],{"class":1182},[539,2546,1191],{"class":545},[539,2548,1194],{"class":545},[539,2550,1198],{"class":1197},[539,2552,1201],{"class":1182},[539,2554,523],{"class":545},[539,2556,493],{"class":1182},[539,2558,523],{"class":545},[539,2560,2561],{"class":1210},"signInAnonymously",[539,2563,2475],{"class":1182},[539,2565,2566],{"class":541,"line":577},[539,2567,1309],{"emptyLinePlaceholder":1308},[539,2569,2570],{"class":541,"line":598},[539,2571,2572],{"class":1170},"\u002F\u002F Sau đó, user có thể link với email\u002FOAuth để trở thành permanent user\n",[539,2574,2575,2577,2579,2581,2583,2585,2587,2589,2591,2593,2595,2597,2599,2602,2604],{"class":541,"line":615},[539,2576,1176],{"class":555},[539,2578,1179],{"class":545},[539,2580,1183],{"class":1182},[539,2582,673],{"class":545},[539,2584,1188],{"class":1182},[539,2586,1191],{"class":545},[539,2588,1194],{"class":545},[539,2590,1198],{"class":1197},[539,2592,1201],{"class":1182},[539,2594,523],{"class":545},[539,2596,493],{"class":1182},[539,2598,523],{"class":545},[539,2600,2601],{"class":1210},"linkIdentity",[539,2603,1214],{"class":1182},[539,2605,546],{"class":545},[539,2607,2608,2610,2612,2614,2616],{"class":541,"line":630},[539,2609,1877],{"class":1221},[539,2611,562],{"class":545},[539,2613,1227],{"class":545},[539,2615,678],{"class":568},[539,2617,1388],{"class":545},[539,2619,2620,2622],{"class":541,"line":652},[539,2621,1191],{"class":545},[539,2623,1303],{"class":1182},[431,2625,2626],{},[435,2627,2628],{},"Identity Linking — quản lý nhiều phương thức đăng nhập cho cùng một user:",[449,2630,2632],{"className":1161,"code":2631,"language":1163,"meta":458,"style":458},"\u002F\u002F Link Google identity vào tài khoản hiện tại\n\u002F\u002F (user đã login bằng email, muốn thêm Google login)\nconst { data, error } = await supabase.auth.linkIdentity({\n  provider: 'google'\n})\n\n\u002F\u002F Link GitHub identity\nconst { data, error } = await supabase.auth.linkIdentity({\n  provider: 'github'\n})\n\n\u002F\u002F Unlink — gỡ bỏ một identity khỏi tài khoản\n\u002F\u002F (chỉ hoạt động khi user còn ít nhất 1 identity khác)\nconst { data, error } = await supabase.auth.unlinkIdentity({\n  identity_id: 'd0d4e701-5a5c-4c8d-b8e7-a1b2c3d4e5f6'\n})\n\n\u002F\u002F Lấy danh sách tất cả identities của user hiện tại\nconst { data, error } = await supabase.auth.getUserIdentities()\n\u002F\u002F data.identities = [\n\u002F\u002F   { id: '...', provider: 'email', identity_data: { email: '...' } },\n\u002F\u002F   { id: '...', provider: 'google', identity_data: { email: '...' } },\n\u002F\u002F ]\n",[456,2633,2634,2639,2644,2676,2688,2694,2698,2703,2735,2747,2753,2757,2762,2767,2800,2813,2819,2823,2828,2859,2864,2869,2874],{"__ignoreMap":458},[539,2635,2636],{"class":541,"line":542},[539,2637,2638],{"class":1170},"\u002F\u002F Link Google identity vào tài khoản hiện tại\n",[539,2640,2641],{"class":541,"line":549},[539,2642,2643],{"class":1170},"\u002F\u002F (user đã login bằng email, muốn thêm Google login)\n",[539,2645,2646,2648,2650,2652,2654,2656,2658,2660,2662,2664,2666,2668,2670,2672,2674],{"class":541,"line":577},[539,2647,1176],{"class":555},[539,2649,1179],{"class":545},[539,2651,1183],{"class":1182},[539,2653,673],{"class":545},[539,2655,1188],{"class":1182},[539,2657,1191],{"class":545},[539,2659,1194],{"class":545},[539,2661,1198],{"class":1197},[539,2663,1201],{"class":1182},[539,2665,523],{"class":545},[539,2667,493],{"class":1182},[539,2669,523],{"class":545},[539,2671,2601],{"class":1210},[539,2673,1214],{"class":1182},[539,2675,546],{"class":545},[539,2677,2678,2680,2682,2684,2686],{"class":541,"line":598},[539,2679,1877],{"class":1221},[539,2681,562],{"class":545},[539,2683,1227],{"class":545},[539,2685,678],{"class":568},[539,2687,1388],{"class":545},[539,2689,2690,2692],{"class":541,"line":615},[539,2691,1191],{"class":545},[539,2693,1303],{"class":1182},[539,2695,2696],{"class":541,"line":630},[539,2697,1309],{"emptyLinePlaceholder":1308},[539,2699,2700],{"class":541,"line":652},[539,2701,2702],{"class":1170},"\u002F\u002F Link GitHub identity\n",[539,2704,2705,2707,2709,2711,2713,2715,2717,2719,2721,2723,2725,2727,2729,2731,2733],{"class":541,"line":686},[539,2706,1176],{"class":555},[539,2708,1179],{"class":545},[539,2710,1183],{"class":1182},[539,2712,673],{"class":545},[539,2714,1188],{"class":1182},[539,2716,1191],{"class":545},[539,2718,1194],{"class":545},[539,2720,1198],{"class":1197},[539,2722,1201],{"class":1182},[539,2724,523],{"class":545},[539,2726,493],{"class":1182},[539,2728,523],{"class":545},[539,2730,2601],{"class":1210},[539,2732,1214],{"class":1182},[539,2734,546],{"class":545},[539,2736,2737,2739,2741,2743,2745],{"class":541,"line":692},[539,2738,1877],{"class":1221},[539,2740,562],{"class":545},[539,2742,1227],{"class":545},[539,2744,2018],{"class":568},[539,2746,1388],{"class":545},[539,2748,2749,2751],{"class":541,"line":706},[539,2750,1191],{"class":545},[539,2752,1303],{"class":1182},[539,2754,2755],{"class":541,"line":727},[539,2756,1309],{"emptyLinePlaceholder":1308},[539,2758,2759],{"class":541,"line":747},[539,2760,2761],{"class":1170},"\u002F\u002F Unlink — gỡ bỏ một identity khỏi tài khoản\n",[539,2763,2764],{"class":541,"line":752},[539,2765,2766],{"class":1170},"\u002F\u002F (chỉ hoạt động khi user còn ít nhất 1 identity khác)\n",[539,2768,2769,2771,2773,2775,2777,2779,2781,2783,2785,2787,2789,2791,2793,2796,2798],{"class":541,"line":773},[539,2770,1176],{"class":555},[539,2772,1179],{"class":545},[539,2774,1183],{"class":1182},[539,2776,673],{"class":545},[539,2778,1188],{"class":1182},[539,2780,1191],{"class":545},[539,2782,1194],{"class":545},[539,2784,1198],{"class":1197},[539,2786,1201],{"class":1182},[539,2788,523],{"class":545},[539,2790,493],{"class":1182},[539,2792,523],{"class":545},[539,2794,2795],{"class":1210},"unlinkIdentity",[539,2797,1214],{"class":1182},[539,2799,546],{"class":545},[539,2801,2802,2805,2807,2809,2811],{"class":541,"line":794},[539,2803,2804],{"class":1221},"  identity_id",[539,2806,562],{"class":545},[539,2808,1227],{"class":545},[539,2810,569],{"class":568},[539,2812,1388],{"class":545},[539,2814,2815,2817],{"class":541,"line":815},[539,2816,1191],{"class":545},[539,2818,1303],{"class":1182},[539,2820,2821],{"class":541,"line":833},[539,2822,1309],{"emptyLinePlaceholder":1308},[539,2824,2825],{"class":541,"line":848},[539,2826,2827],{"class":1170},"\u002F\u002F Lấy danh sách tất cả identities của user hiện tại\n",[539,2829,2830,2832,2834,2836,2838,2840,2842,2844,2846,2848,2850,2852,2854,2857],{"class":541,"line":1391},[539,2831,1176],{"class":555},[539,2833,1179],{"class":545},[539,2835,1183],{"class":1182},[539,2837,673],{"class":545},[539,2839,1188],{"class":1182},[539,2841,1191],{"class":545},[539,2843,1194],{"class":545},[539,2845,1198],{"class":1197},[539,2847,1201],{"class":1182},[539,2849,523],{"class":545},[539,2851,493],{"class":1182},[539,2853,523],{"class":545},[539,2855,2856],{"class":1210},"getUserIdentities",[539,2858,2475],{"class":1182},[539,2860,2861],{"class":541,"line":1398},[539,2862,2863],{"class":1170},"\u002F\u002F data.identities = [\n",[539,2865,2866],{"class":541,"line":1403},[539,2867,2868],{"class":1170},"\u002F\u002F   { id: '...', provider: 'email', identity_data: { email: '...' } },\n",[539,2870,2871],{"class":541,"line":1409},[539,2872,2873],{"class":1170},"\u002F\u002F   { id: '...', provider: 'google', identity_data: { email: '...' } },\n",[539,2875,2877],{"class":541,"line":2876},23,[539,2878,2879],{"class":1170},"\u002F\u002F ]\n",[507,2881,2882,2885,2886,2889],{"color":1036,"icon":1037},[435,2883,2884],{},"Interview point — Identity Linking:"," Một user có thể link nhiều provider vào cùng một account (ví dụ: email + Google + GitHub). Khi user login bằng bất kỳ provider nào đã linked, họ đều vào cùng một account. ",[456,2887,2888],{},"getUserIdentities()"," trả về tất cả identities giúp UI hiển thị \"Connected accounts\". Khi unlink, user phải còn ít nhất 1 identity — không thể xóa identity cuối cùng.",[444,2891,2893],{"id":2892},"_8-web3-wallet-ethereum-solana","8. Web3 Wallet (Ethereum \u002F Solana)",[449,2895,2897],{"className":1161,"code":2896,"language":1163,"meta":458,"style":458},"const { data } = await supabase.auth.signInWithWeb3({\n  provider: 'ethereum'\n})\n",[456,2898,2899,2928,2941],{"__ignoreMap":458},[539,2900,2901,2903,2905,2907,2909,2911,2913,2915,2917,2919,2921,2924,2926],{"class":541,"line":542},[539,2902,1176],{"class":555},[539,2904,1179],{"class":545},[539,2906,2455],{"class":1182},[539,2908,1191],{"class":545},[539,2910,1194],{"class":545},[539,2912,1198],{"class":1197},[539,2914,1201],{"class":1182},[539,2916,523],{"class":545},[539,2918,493],{"class":1182},[539,2920,523],{"class":545},[539,2922,2923],{"class":1210},"signInWithWeb3",[539,2925,1214],{"class":1182},[539,2927,546],{"class":545},[539,2929,2930,2932,2934,2936,2939],{"class":541,"line":549},[539,2931,1877],{"class":1221},[539,2933,562],{"class":545},[539,2935,1227],{"class":545},[539,2937,2938],{"class":568},"ethereum",[539,2940,1388],{"class":545},[539,2942,2943,2945],{"class":541,"line":577},[539,2944,1191],{"class":545},[539,2946,1303],{"class":1182},[431,2948,2949,2950,2953],{},"Hỗ trợ ",[435,2951,2952],{},"MetaMask, WalletConnect"," và các wallet Ethereum\u002FSolana.",[1146,2955],{},[427,2957,2959],{"id":2958},"session-management","Session Management",[431,2961,2962,2963,2966,2967,2970],{},"Session được quản lý qua ",[435,2964,2965],{},"access token"," (JWT, thời hạn 1 giờ) và ",[435,2968,2969],{},"refresh token"," (dùng để lấy access token mới):",[449,2972,2974],{"className":1161,"code":2973,"language":1163,"meta":458,"style":458},"\u002F\u002F Lấy session hiện tại\nconst { data: { session } } = await supabase.auth.getSession()\n\n\u002F\u002F Lấy user hiện tại\nconst { data: { user } } = await supabase.auth.getUser()\n\n\u002F\u002F Refresh session (access token hết hạn -> dùng refresh token lấy mới)\nconst { data, error } = await supabase.auth.refreshSession()\n\n\u002F\u002F Sign out\nconst { error } = await supabase.auth.signOut()\n",[456,2975,2976,2981,3018,3022,3027,3063,3067,3072,3103,3107,3112],{"__ignoreMap":458},[539,2977,2978],{"class":541,"line":542},[539,2979,2980],{"class":1170},"\u002F\u002F Lấy session hiện tại\n",[539,2982,2983,2985,2987,2989,2991,2993,2996,2998,3001,3003,3005,3007,3009,3011,3013,3016],{"class":541,"line":549},[539,2984,1176],{"class":555},[539,2986,1179],{"class":545},[539,2988,1183],{"class":1221},[539,2990,562],{"class":545},[539,2992,1179],{"class":545},[539,2994,2995],{"class":1182}," session ",[539,2997,1191],{"class":545},[539,2999,3000],{"class":545}," }",[539,3002,1194],{"class":545},[539,3004,1198],{"class":1197},[539,3006,1201],{"class":1182},[539,3008,523],{"class":545},[539,3010,493],{"class":1182},[539,3012,523],{"class":545},[539,3014,3015],{"class":1210},"getSession",[539,3017,2475],{"class":1182},[539,3019,3020],{"class":541,"line":577},[539,3021,1309],{"emptyLinePlaceholder":1308},[539,3023,3024],{"class":541,"line":598},[539,3025,3026],{"class":1170},"\u002F\u002F Lấy user hiện tại\n",[539,3028,3029,3031,3033,3035,3037,3039,3042,3044,3046,3048,3050,3052,3054,3056,3058,3061],{"class":541,"line":615},[539,3030,1176],{"class":555},[539,3032,1179],{"class":545},[539,3034,1183],{"class":1221},[539,3036,562],{"class":545},[539,3038,1179],{"class":545},[539,3040,3041],{"class":1182}," user ",[539,3043,1191],{"class":545},[539,3045,3000],{"class":545},[539,3047,1194],{"class":545},[539,3049,1198],{"class":1197},[539,3051,1201],{"class":1182},[539,3053,523],{"class":545},[539,3055,493],{"class":1182},[539,3057,523],{"class":545},[539,3059,3060],{"class":1210},"getUser",[539,3062,2475],{"class":1182},[539,3064,3065],{"class":541,"line":630},[539,3066,1309],{"emptyLinePlaceholder":1308},[539,3068,3069],{"class":541,"line":652},[539,3070,3071],{"class":1170},"\u002F\u002F Refresh session (access token hết hạn -> dùng refresh token lấy mới)\n",[539,3073,3074,3076,3078,3080,3082,3084,3086,3088,3090,3092,3094,3096,3098,3101],{"class":541,"line":686},[539,3075,1176],{"class":555},[539,3077,1179],{"class":545},[539,3079,1183],{"class":1182},[539,3081,673],{"class":545},[539,3083,1188],{"class":1182},[539,3085,1191],{"class":545},[539,3087,1194],{"class":545},[539,3089,1198],{"class":1197},[539,3091,1201],{"class":1182},[539,3093,523],{"class":545},[539,3095,493],{"class":1182},[539,3097,523],{"class":545},[539,3099,3100],{"class":1210},"refreshSession",[539,3102,2475],{"class":1182},[539,3104,3105],{"class":541,"line":692},[539,3106,1309],{"emptyLinePlaceholder":1308},[539,3108,3109],{"class":541,"line":706},[539,3110,3111],{"class":1170},"\u002F\u002F Sign out\n",[539,3113,3114,3116,3118,3120,3122,3124,3126,3128,3130,3132,3134,3137],{"class":541,"line":727},[539,3115,1176],{"class":555},[539,3117,1179],{"class":545},[539,3119,1188],{"class":1182},[539,3121,1191],{"class":545},[539,3123,1194],{"class":545},[539,3125,1198],{"class":1197},[539,3127,1201],{"class":1182},[539,3129,523],{"class":545},[539,3131,493],{"class":1182},[539,3133,523],{"class":545},[539,3135,3136],{"class":1210},"signOut",[539,3138,2475],{"class":1182},[444,3140,3142],{"id":3141},"theo-dõi-thay-đổi-auth-state","Theo dõi thay đổi Auth State",[449,3144,3146],{"className":1161,"code":3145,"language":1163,"meta":458,"style":458},"\u002F\u002F Listen auth state changes\nsupabase.auth.onAuthStateChange((event, session) => {\n  switch (event) {\n    case 'INITIAL_SESSION':\n      console.log('Initial session loaded:', session)\n      break\n    case 'SIGNED_IN':\n      console.log('User signed in:', session.user)\n      break\n    case 'SIGNED_OUT':\n      console.log('User signed out')\n      break\n    case 'TOKEN_REFRESHED':\n      console.log('Token refreshed')\n      break\n    case 'USER_UPDATED':\n      console.log('User updated:', session.user)\n      break\n    case 'USER_DELETED':\n      console.log('User deleted')\n      break\n    case 'PASSWORD_RECOVERY':\n      console.log('Password recovery event')\n      break\n    case 'MFA_CHALLENGE_VERIFIED':\n      console.log('MFA verified')\n      break\n  }\n})\n",[456,3147,3148,3153,3186,3201,3216,3241,3246,3259,3287,3291,3303,3322,3326,3339,3358,3362,3375,3402,3406,3419,3438,3442,3455,3474,3479,3493,3513,3518,3523],{"__ignoreMap":458},[539,3149,3150],{"class":541,"line":542},[539,3151,3152],{"class":1170},"\u002F\u002F Listen auth state changes\n",[539,3154,3155,3158,3160,3162,3164,3166,3168,3170,3174,3176,3178,3181,3184],{"class":541,"line":549},[539,3156,3157],{"class":1182},"supabase",[539,3159,523],{"class":545},[539,3161,493],{"class":1182},[539,3163,523],{"class":545},[539,3165,1140],{"class":1210},[539,3167,1214],{"class":1182},[539,3169,1214],{"class":545},[539,3171,3173],{"class":3172},"sHdIc","event",[539,3175,673],{"class":545},[539,3177,1673],{"class":3172},[539,3179,3180],{"class":545},")",[539,3182,3183],{"class":555}," =>",[539,3185,627],{"class":545},[539,3187,3188,3191,3194,3196,3199],{"class":541,"line":577},[539,3189,3190],{"class":1197},"  switch",[539,3192,3193],{"class":1221}," (",[539,3195,3173],{"class":1182},[539,3197,3198],{"class":1221},") ",[539,3200,546],{"class":545},[539,3202,3203,3206,3208,3211,3213],{"class":541,"line":598},[539,3204,3205],{"class":1197},"    case",[539,3207,1227],{"class":545},[539,3209,3210],{"class":568},"INITIAL_SESSION",[539,3212,1232],{"class":545},[539,3214,3215],{"class":545},":\n",[539,3217,3218,3221,3223,3226,3228,3230,3233,3235,3237,3239],{"class":541,"line":615},[539,3219,3220],{"class":1182},"      console",[539,3222,523],{"class":545},[539,3224,3225],{"class":1210},"log",[539,3227,1214],{"class":1221},[539,3229,1232],{"class":545},[539,3231,3232],{"class":568},"Initial session loaded:",[539,3234,1232],{"class":545},[539,3236,673],{"class":545},[539,3238,1673],{"class":1182},[539,3240,1303],{"class":1221},[539,3242,3243],{"class":541,"line":630},[539,3244,3245],{"class":1197},"      break\n",[539,3247,3248,3250,3252,3255,3257],{"class":541,"line":652},[539,3249,3205],{"class":1197},[539,3251,1227],{"class":545},[539,3253,3254],{"class":568},"SIGNED_IN",[539,3256,1232],{"class":545},[539,3258,3215],{"class":545},[539,3260,3261,3263,3265,3267,3269,3271,3274,3276,3278,3280,3282,3285],{"class":541,"line":686},[539,3262,3220],{"class":1182},[539,3264,523],{"class":545},[539,3266,3225],{"class":1210},[539,3268,1214],{"class":1221},[539,3270,1232],{"class":545},[539,3272,3273],{"class":568},"User signed in:",[539,3275,1232],{"class":545},[539,3277,673],{"class":545},[539,3279,1673],{"class":1182},[539,3281,523],{"class":545},[539,3283,3284],{"class":1182},"user",[539,3286,1303],{"class":1221},[539,3288,3289],{"class":541,"line":692},[539,3290,3245],{"class":1197},[539,3292,3293,3295,3297,3299,3301],{"class":541,"line":706},[539,3294,3205],{"class":1197},[539,3296,1227],{"class":545},[539,3298,1144],{"class":568},[539,3300,1232],{"class":545},[539,3302,3215],{"class":545},[539,3304,3305,3307,3309,3311,3313,3315,3318,3320],{"class":541,"line":727},[539,3306,3220],{"class":1182},[539,3308,523],{"class":545},[539,3310,3225],{"class":1210},[539,3312,1214],{"class":1221},[539,3314,1232],{"class":545},[539,3316,3317],{"class":568},"User signed out",[539,3319,1232],{"class":545},[539,3321,1303],{"class":1221},[539,3323,3324],{"class":541,"line":747},[539,3325,3245],{"class":1197},[539,3327,3328,3330,3332,3335,3337],{"class":541,"line":752},[539,3329,3205],{"class":1197},[539,3331,1227],{"class":545},[539,3333,3334],{"class":568},"TOKEN_REFRESHED",[539,3336,1232],{"class":545},[539,3338,3215],{"class":545},[539,3340,3341,3343,3345,3347,3349,3351,3354,3356],{"class":541,"line":773},[539,3342,3220],{"class":1182},[539,3344,523],{"class":545},[539,3346,3225],{"class":1210},[539,3348,1214],{"class":1221},[539,3350,1232],{"class":545},[539,3352,3353],{"class":568},"Token refreshed",[539,3355,1232],{"class":545},[539,3357,1303],{"class":1221},[539,3359,3360],{"class":541,"line":794},[539,3361,3245],{"class":1197},[539,3363,3364,3366,3368,3371,3373],{"class":541,"line":815},[539,3365,3205],{"class":1197},[539,3367,1227],{"class":545},[539,3369,3370],{"class":568},"USER_UPDATED",[539,3372,1232],{"class":545},[539,3374,3215],{"class":545},[539,3376,3377,3379,3381,3383,3385,3387,3390,3392,3394,3396,3398,3400],{"class":541,"line":833},[539,3378,3220],{"class":1182},[539,3380,523],{"class":545},[539,3382,3225],{"class":1210},[539,3384,1214],{"class":1221},[539,3386,1232],{"class":545},[539,3388,3389],{"class":568},"User updated:",[539,3391,1232],{"class":545},[539,3393,673],{"class":545},[539,3395,1673],{"class":1182},[539,3397,523],{"class":545},[539,3399,3284],{"class":1182},[539,3401,1303],{"class":1221},[539,3403,3404],{"class":541,"line":848},[539,3405,3245],{"class":1197},[539,3407,3408,3410,3412,3415,3417],{"class":541,"line":1391},[539,3409,3205],{"class":1197},[539,3411,1227],{"class":545},[539,3413,3414],{"class":568},"USER_DELETED",[539,3416,1232],{"class":545},[539,3418,3215],{"class":545},[539,3420,3421,3423,3425,3427,3429,3431,3434,3436],{"class":541,"line":1398},[539,3422,3220],{"class":1182},[539,3424,523],{"class":545},[539,3426,3225],{"class":1210},[539,3428,1214],{"class":1221},[539,3430,1232],{"class":545},[539,3432,3433],{"class":568},"User deleted",[539,3435,1232],{"class":545},[539,3437,1303],{"class":1221},[539,3439,3440],{"class":541,"line":1403},[539,3441,3245],{"class":1197},[539,3443,3444,3446,3448,3451,3453],{"class":541,"line":1409},[539,3445,3205],{"class":1197},[539,3447,1227],{"class":545},[539,3449,3450],{"class":568},"PASSWORD_RECOVERY",[539,3452,1232],{"class":545},[539,3454,3215],{"class":545},[539,3456,3457,3459,3461,3463,3465,3467,3470,3472],{"class":541,"line":2876},[539,3458,3220],{"class":1182},[539,3460,523],{"class":545},[539,3462,3225],{"class":1210},[539,3464,1214],{"class":1221},[539,3466,1232],{"class":545},[539,3468,3469],{"class":568},"Password recovery event",[539,3471,1232],{"class":545},[539,3473,1303],{"class":1221},[539,3475,3477],{"class":541,"line":3476},24,[539,3478,3245],{"class":1197},[539,3480,3482,3484,3486,3489,3491],{"class":541,"line":3481},25,[539,3483,3205],{"class":1197},[539,3485,1227],{"class":545},[539,3487,3488],{"class":568},"MFA_CHALLENGE_VERIFIED",[539,3490,1232],{"class":545},[539,3492,3215],{"class":545},[539,3494,3496,3498,3500,3502,3504,3506,3509,3511],{"class":541,"line":3495},26,[539,3497,3220],{"class":1182},[539,3499,523],{"class":545},[539,3501,3225],{"class":1210},[539,3503,1214],{"class":1221},[539,3505,1232],{"class":545},[539,3507,3508],{"class":568},"MFA verified",[539,3510,1232],{"class":545},[539,3512,1303],{"class":1221},[539,3514,3516],{"class":541,"line":3515},27,[539,3517,3245],{"class":1197},[539,3519,3521],{"class":541,"line":3520},28,[539,3522,1296],{"class":545},[539,3524,3526,3528],{"class":541,"line":3525},29,[539,3527,1191],{"class":545},[539,3529,1303],{"class":1182},[431,3531,3532],{},[435,3533,3534,3535,3537],{},"Giải thích từng ",[456,3536,1140],{}," event:",[858,3539,3540,3553],{},[861,3541,3542],{},[864,3543,3544,3547,3550],{},[867,3545,3546],{},"Event",[867,3548,3549],{},"Khi nào fire",[867,3551,3552],{},"Hành động điển hình",[877,3554,3555,3567,3579,3591,3603,3615,3627,3639],{},[864,3556,3557,3561,3564],{},[882,3558,3559],{},[456,3560,3210],{},[882,3562,3563],{},"App vừa load, session được khôi phục từ localStorage",[882,3565,3566],{},"Khởi tạo UI dựa trên session có sẵn",[864,3568,3569,3573,3576],{},[882,3570,3571],{},[456,3572,3254],{},[882,3574,3575],{},"User vừa sign in thành công (email, OAuth, magic link, OTP)",[882,3577,3578],{},"Redirect về dashboard, fetch user data",[864,3580,3581,3585,3588],{},[882,3582,3583],{},[456,3584,1144],{},[882,3586,3587],{},"User sign out hoặc refresh token hết hạn",[882,3589,3590],{},"Xóa local state, redirect về login",[864,3592,3593,3597,3600],{},[882,3594,3595],{},[456,3596,3334],{},[882,3598,3599],{},"Access token được refresh tự động",[882,3601,3602],{},"Cập nhật token trong app state (thường tự động)",[864,3604,3605,3609,3612],{},[882,3606,3607],{},[456,3608,3370],{},[882,3610,3611],{},"User metadata thay đổi (updateUser, email change)",[882,3613,3614],{},"Cập nhật UI với thông tin mới",[864,3616,3617,3621,3624],{},[882,3618,3619],{},[456,3620,3414],{},[882,3622,3623],{},"User bị xóa (từ admin API hoặc tự xóa)",[882,3625,3626],{},"Xóa local data, redirect về trang chủ",[864,3628,3629,3633,3636],{},[882,3630,3631],{},[456,3632,3450],{},[882,3634,3635],{},"User click link reset password từ email",[882,3637,3638],{},"Hiển thị form đặt mật khẩu mới",[864,3640,3641,3645,3648],{},[882,3642,3643],{},[456,3644,3488],{},[882,3646,3647],{},"User vượt qua MFA challenge thành công",[882,3649,3650],{},"Nâng session lên aal2, mở khóa tính năng nhạy cảm",[507,3652,3653,3661],{"color":1036,"icon":1037},[431,3654,3655,3657,3658,3660],{},[435,3656,1040],{}," ",[456,3659,1140],{}," là listener quan trọng nhất trong Supabase Auth. Dùng nó để:",[1546,3662,3663,3666,3680,3686,3692],{},[463,3664,3665],{},"Sync auth state với UI framework (Vue reactivity, React context)",[463,3667,3668,3669,3671,3672,498,3675,3671,3677],{},"Bảo vệ routes: ",[456,3670,1144],{}," -> redirect ",[456,3673,3674],{},"\u002Flogin",[456,3676,3254],{},[456,3678,3679],{},"\u002Fdashboard",[463,3681,3682,3683,3685],{},"Xử lý MFA flow: ",[456,3684,3488],{}," -> cập nhật UI",[463,3687,3688,3689,3691],{},"Cleanup: ",[456,3690,3414],{}," -> xóa local data",[463,3693,3694,3696],{},[456,3695,3210],{}," giúp phân biệt \"đang loading session\" và \"đã biết user chưa login\"",[1146,3698],{},[427,3700,3702],{"id":3701},"server-side-auth-xác-thực-trong-api-routes","Server-Side Auth — Xác thực trong API Routes",[431,3704,3705],{},"Khi xây dựng API với Next.js, Nuxt, SvelteKit, bạn cần xác thực user ở server side:",[444,3707,3709],{"id":3708},"nextjs-ssr-pattern","Next.js SSR Pattern",[449,3711,3715],{"className":3712,"code":3713,"language":3714,"meta":458,"style":458},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F app\u002Fapi\u002Fprotected\u002Froute.ts (Next.js App Router)\nimport { createServerClient } from '@supabase\u002Fssr'\nimport { cookies } from 'next\u002Fheaders'\nimport { NextResponse } from 'next\u002Fserver'\n\nexport async function GET() {\n  const cookieStore = await cookies()\n  const supabase = createServerClient(\n    process.env.SUPABASE_URL!,\n    process.env.SUPABASE_ANON_KEY!,\n    {\n      cookies: {\n        get(name) { return cookieStore.get(name)?.value },\n        set(name, value, options) { cookieStore.set({ name, value, ...options }) },\n        remove(name, options) { cookieStore.set({ name, value: '', ...options }) },\n      },\n    }\n  )\n\n  const { data: { user } } = await supabase.auth.getUser()\n  if (!user) {\n    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n  }\n\n  return NextResponse.json({ user })\n}\n","typescript",[456,3716,3717,3722,3744,3764,3784,3788,3807,3823,3836,3854,3869,3874,3883,3922,3979,4029,4034,4038,4043,4047,4082,4098,4142,4146,4150,4171],{"__ignoreMap":458},[539,3718,3719],{"class":541,"line":542},[539,3720,3721],{"class":1170},"\u002F\u002F app\u002Fapi\u002Fprotected\u002Froute.ts (Next.js App Router)\n",[539,3723,3724,3727,3729,3732,3734,3737,3739,3742],{"class":541,"line":549},[539,3725,3726],{"class":1197},"import",[539,3728,1179],{"class":545},[539,3730,3731],{"class":1182}," createServerClient",[539,3733,3000],{"class":545},[539,3735,3736],{"class":1197}," from",[539,3738,1227],{"class":545},[539,3740,3741],{"class":568},"@supabase\u002Fssr",[539,3743,1388],{"class":545},[539,3745,3746,3748,3750,3753,3755,3757,3759,3762],{"class":541,"line":577},[539,3747,3726],{"class":1197},[539,3749,1179],{"class":545},[539,3751,3752],{"class":1182}," cookies",[539,3754,3000],{"class":545},[539,3756,3736],{"class":1197},[539,3758,1227],{"class":545},[539,3760,3761],{"class":568},"next\u002Fheaders",[539,3763,1388],{"class":545},[539,3765,3766,3768,3770,3773,3775,3777,3779,3782],{"class":541,"line":598},[539,3767,3726],{"class":1197},[539,3769,1179],{"class":545},[539,3771,3772],{"class":1182}," NextResponse",[539,3774,3000],{"class":545},[539,3776,3736],{"class":1197},[539,3778,1227],{"class":545},[539,3780,3781],{"class":568},"next\u002Fserver",[539,3783,1388],{"class":545},[539,3785,3786],{"class":541,"line":615},[539,3787,1309],{"emptyLinePlaceholder":1308},[539,3789,3790,3793,3796,3799,3802,3805],{"class":541,"line":630},[539,3791,3792],{"class":1197},"export",[539,3794,3795],{"class":555}," async",[539,3797,3798],{"class":555}," function",[539,3800,3801],{"class":1210}," GET",[539,3803,3804],{"class":545},"()",[539,3806,627],{"class":545},[539,3808,3809,3812,3815,3817,3819,3821],{"class":541,"line":652},[539,3810,3811],{"class":555},"  const",[539,3813,3814],{"class":1182}," cookieStore",[539,3816,1194],{"class":545},[539,3818,1198],{"class":1197},[539,3820,3752],{"class":1210},[539,3822,2475],{"class":1221},[539,3824,3825,3827,3829,3831,3833],{"class":541,"line":686},[539,3826,3811],{"class":555},[539,3828,1201],{"class":1182},[539,3830,1194],{"class":545},[539,3832,3731],{"class":1210},[539,3834,3835],{"class":1221},"(\n",[539,3837,3838,3841,3843,3846,3848,3851],{"class":541,"line":692},[539,3839,3840],{"class":1182},"    process",[539,3842,523],{"class":545},[539,3844,3845],{"class":1182},"env",[539,3847,523],{"class":545},[539,3849,3850],{"class":1182},"SUPABASE_URL",[539,3852,3853],{"class":545},"!,\n",[539,3855,3856,3858,3860,3862,3864,3867],{"class":541,"line":706},[539,3857,3840],{"class":1182},[539,3859,523],{"class":545},[539,3861,3845],{"class":1182},[539,3863,523],{"class":545},[539,3865,3866],{"class":1182},"SUPABASE_ANON_KEY",[539,3868,3853],{"class":545},[539,3870,3871],{"class":541,"line":727},[539,3872,3873],{"class":545},"    {\n",[539,3875,3876,3879,3881],{"class":541,"line":747},[539,3877,3878],{"class":1221},"      cookies",[539,3880,562],{"class":545},[539,3882,627],{"class":545},[539,3884,3885,3888,3890,3893,3895,3897,3900,3902,3904,3907,3909,3911,3913,3916,3919],{"class":541,"line":752},[539,3886,3887],{"class":1221},"        get",[539,3889,1214],{"class":545},[539,3891,3892],{"class":3172},"name",[539,3894,3180],{"class":545},[539,3896,1179],{"class":545},[539,3898,3899],{"class":1197}," return",[539,3901,3814],{"class":1182},[539,3903,523],{"class":545},[539,3905,3906],{"class":1210},"get",[539,3908,1214],{"class":1221},[539,3910,3892],{"class":1182},[539,3912,3180],{"class":1221},[539,3914,3915],{"class":545},"?.",[539,3917,3918],{"class":1182},"value",[539,3920,3921],{"class":545}," },\n",[539,3923,3924,3927,3929,3931,3933,3936,3938,3941,3943,3945,3947,3949,3952,3954,3957,3960,3962,3964,3966,3969,3972,3974,3976],{"class":541,"line":773},[539,3925,3926],{"class":1221},"        set",[539,3928,1214],{"class":545},[539,3930,3892],{"class":3172},[539,3932,673],{"class":545},[539,3934,3935],{"class":3172}," value",[539,3937,673],{"class":545},[539,3939,3940],{"class":3172}," options",[539,3942,3180],{"class":545},[539,3944,1179],{"class":545},[539,3946,3814],{"class":1182},[539,3948,523],{"class":545},[539,3950,3951],{"class":1210},"set",[539,3953,1214],{"class":1221},[539,3955,3956],{"class":545},"{",[539,3958,3959],{"class":1182}," name",[539,3961,673],{"class":545},[539,3963,3935],{"class":1182},[539,3965,673],{"class":545},[539,3967,3968],{"class":545}," ...",[539,3970,3971],{"class":1182},"options",[539,3973,3000],{"class":545},[539,3975,3198],{"class":1221},[539,3977,3978],{"class":545},"},\n",[539,3980,3981,3984,3986,3988,3990,3992,3994,3996,3998,4000,4002,4004,4006,4008,4010,4012,4014,4017,4019,4021,4023,4025,4027],{"class":541,"line":794},[539,3982,3983],{"class":1221},"        remove",[539,3985,1214],{"class":545},[539,3987,3892],{"class":3172},[539,3989,673],{"class":545},[539,3991,3940],{"class":3172},[539,3993,3180],{"class":545},[539,3995,1179],{"class":545},[539,3997,3814],{"class":1182},[539,3999,523],{"class":545},[539,4001,3951],{"class":1210},[539,4003,1214],{"class":1221},[539,4005,3956],{"class":545},[539,4007,3959],{"class":1182},[539,4009,673],{"class":545},[539,4011,3935],{"class":1221},[539,4013,562],{"class":545},[539,4015,4016],{"class":545}," ''",[539,4018,673],{"class":545},[539,4020,3968],{"class":545},[539,4022,3971],{"class":1182},[539,4024,3000],{"class":545},[539,4026,3198],{"class":1221},[539,4028,3978],{"class":545},[539,4030,4031],{"class":541,"line":815},[539,4032,4033],{"class":545},"      },\n",[539,4035,4036],{"class":541,"line":833},[539,4037,1291],{"class":545},[539,4039,4040],{"class":541,"line":848},[539,4041,4042],{"class":1221},"  )\n",[539,4044,4045],{"class":541,"line":1391},[539,4046,1309],{"emptyLinePlaceholder":1308},[539,4048,4049,4051,4053,4055,4057,4059,4062,4064,4066,4068,4070,4072,4074,4076,4078,4080],{"class":541,"line":1398},[539,4050,3811],{"class":555},[539,4052,1179],{"class":545},[539,4054,1183],{"class":1221},[539,4056,562],{"class":545},[539,4058,1179],{"class":545},[539,4060,4061],{"class":1182}," user",[539,4063,3000],{"class":545},[539,4065,3000],{"class":545},[539,4067,1194],{"class":545},[539,4069,1198],{"class":1197},[539,4071,1201],{"class":1182},[539,4073,523],{"class":545},[539,4075,493],{"class":1182},[539,4077,523],{"class":545},[539,4079,3060],{"class":1210},[539,4081,2475],{"class":1221},[539,4083,4084,4087,4089,4092,4094,4096],{"class":541,"line":1403},[539,4085,4086],{"class":1197},"  if",[539,4088,3193],{"class":1221},[539,4090,4091],{"class":545},"!",[539,4093,3284],{"class":1182},[539,4095,3198],{"class":1221},[539,4097,546],{"class":545},[539,4099,4100,4103,4105,4107,4109,4111,4113,4116,4118,4120,4123,4125,4128,4130,4133,4135,4138,4140],{"class":541,"line":1409},[539,4101,4102],{"class":1197},"    return",[539,4104,3772],{"class":1182},[539,4106,523],{"class":545},[539,4108,535],{"class":1210},[539,4110,1214],{"class":1221},[539,4112,3956],{"class":545},[539,4114,4115],{"class":1221}," error",[539,4117,562],{"class":545},[539,4119,1227],{"class":545},[539,4121,4122],{"class":568},"Unauthorized",[539,4124,1232],{"class":545},[539,4126,4127],{"class":545}," },",[539,4129,1179],{"class":545},[539,4131,4132],{"class":1221}," status",[539,4134,562],{"class":545},[539,4136,4137],{"class":827}," 401",[539,4139,3000],{"class":545},[539,4141,1303],{"class":1221},[539,4143,4144],{"class":541,"line":2876},[539,4145,1296],{"class":545},[539,4147,4148],{"class":541,"line":3476},[539,4149,1309],{"emptyLinePlaceholder":1308},[539,4151,4152,4155,4157,4159,4161,4163,4165,4167,4169],{"class":541,"line":3481},[539,4153,4154],{"class":1197},"  return",[539,4156,3772],{"class":1182},[539,4158,523],{"class":545},[539,4160,535],{"class":1210},[539,4162,1214],{"class":1221},[539,4164,3956],{"class":545},[539,4166,4061],{"class":1182},[539,4168,3000],{"class":545},[539,4170,1303],{"class":1221},[539,4172,4173],{"class":541,"line":3495},[539,4174,851],{"class":545},[444,4176,4178],{"id":4177},"nuxt-ssr-pattern","Nuxt SSR Pattern",[449,4180,4182],{"className":3712,"code":4181,"language":3714,"meta":458,"style":458},"\u002F\u002F server\u002Fapi\u002Fprotected.get.ts (Nuxt 3)\nimport { serverSupabaseUser } from '#supabase\u002Fserver'\n\nexport default defineEventHandler(async (event) => {\n  const user = await serverSupabaseUser(event)\n  if (!user) {\n    throw createError({ statusCode: 401, message: 'Unauthorized' })\n  }\n  return { user }\n})\n",[456,4183,4184,4189,4209,4213,4238,4256,4270,4306,4310,4321],{"__ignoreMap":458},[539,4185,4186],{"class":541,"line":542},[539,4187,4188],{"class":1170},"\u002F\u002F server\u002Fapi\u002Fprotected.get.ts (Nuxt 3)\n",[539,4190,4191,4193,4195,4198,4200,4202,4204,4207],{"class":541,"line":549},[539,4192,3726],{"class":1197},[539,4194,1179],{"class":545},[539,4196,4197],{"class":1182}," serverSupabaseUser",[539,4199,3000],{"class":545},[539,4201,3736],{"class":1197},[539,4203,1227],{"class":545},[539,4205,4206],{"class":568},"#supabase\u002Fserver",[539,4208,1388],{"class":545},[539,4210,4211],{"class":541,"line":577},[539,4212,1309],{"emptyLinePlaceholder":1308},[539,4214,4215,4217,4220,4223,4225,4228,4230,4232,4234,4236],{"class":541,"line":598},[539,4216,3792],{"class":1197},[539,4218,4219],{"class":1197}," default",[539,4221,4222],{"class":1210}," defineEventHandler",[539,4224,1214],{"class":1182},[539,4226,4227],{"class":555},"async",[539,4229,3193],{"class":545},[539,4231,3173],{"class":3172},[539,4233,3180],{"class":545},[539,4235,3183],{"class":555},[539,4237,627],{"class":545},[539,4239,4240,4242,4244,4246,4248,4250,4252,4254],{"class":541,"line":615},[539,4241,3811],{"class":555},[539,4243,4061],{"class":1182},[539,4245,1194],{"class":545},[539,4247,1198],{"class":1197},[539,4249,4197],{"class":1210},[539,4251,1214],{"class":1221},[539,4253,3173],{"class":1182},[539,4255,1303],{"class":1221},[539,4257,4258,4260,4262,4264,4266,4268],{"class":541,"line":630},[539,4259,4086],{"class":1197},[539,4261,3193],{"class":1221},[539,4263,4091],{"class":545},[539,4265,3284],{"class":1182},[539,4267,3198],{"class":1221},[539,4269,546],{"class":545},[539,4271,4272,4275,4278,4280,4282,4285,4287,4289,4291,4294,4296,4298,4300,4302,4304],{"class":541,"line":652},[539,4273,4274],{"class":1197},"    throw",[539,4276,4277],{"class":1210}," createError",[539,4279,1214],{"class":1221},[539,4281,3956],{"class":545},[539,4283,4284],{"class":1221}," statusCode",[539,4286,562],{"class":545},[539,4288,4137],{"class":827},[539,4290,673],{"class":545},[539,4292,4293],{"class":1221}," message",[539,4295,562],{"class":545},[539,4297,1227],{"class":545},[539,4299,4122],{"class":568},[539,4301,1232],{"class":545},[539,4303,3000],{"class":545},[539,4305,1303],{"class":1221},[539,4307,4308],{"class":541,"line":686},[539,4309,1296],{"class":545},[539,4311,4312,4314,4316,4318],{"class":541,"line":692},[539,4313,4154],{"class":1197},[539,4315,1179],{"class":545},[539,4317,4061],{"class":1182},[539,4319,4320],{"class":545}," }\n",[539,4322,4323,4325],{"class":541,"line":706},[539,4324,1191],{"class":545},[539,4326,1303],{"class":1182},[444,4328,4330],{"id":4329},"sveltekit-ssr-pattern","SvelteKit SSR Pattern",[449,4332,4334],{"className":3712,"code":4333,"language":3714,"meta":458,"style":458},"\u002F\u002F src\u002Froutes\u002Fapi\u002Fprotected\u002F+server.ts (SvelteKit)\nimport { createServerClient } from '@supabase\u002Fssr'\nimport type { RequestHandler } from '.\u002F$types'\n\nexport const GET: RequestHandler = async ({ cookies }) => {\n  const supabase = createServerClient(\n    process.env.SUPABASE_URL!,\n    process.env.SUPABASE_ANON_KEY!,\n    { cookies: { \u002F* ... *\u002F } }\n  )\n\n  const { data: { user } } = await supabase.auth.getUser()\n  if (!user) return new Response('Unauthorized', { status: 401 })\n  return new Response(JSON.stringify({ user }))\n}\n",[456,4335,4336,4341,4359,4382,4386,4415,4427,4441,4455,4473,4477,4481,4515,4558,4587],{"__ignoreMap":458},[539,4337,4338],{"class":541,"line":542},[539,4339,4340],{"class":1170},"\u002F\u002F src\u002Froutes\u002Fapi\u002Fprotected\u002F+server.ts (SvelteKit)\n",[539,4342,4343,4345,4347,4349,4351,4353,4355,4357],{"class":541,"line":549},[539,4344,3726],{"class":1197},[539,4346,1179],{"class":545},[539,4348,3731],{"class":1182},[539,4350,3000],{"class":545},[539,4352,3736],{"class":1197},[539,4354,1227],{"class":545},[539,4356,3741],{"class":568},[539,4358,1388],{"class":545},[539,4360,4361,4363,4366,4368,4371,4373,4375,4377,4380],{"class":541,"line":577},[539,4362,3726],{"class":1197},[539,4364,4365],{"class":1197}," type",[539,4367,1179],{"class":545},[539,4369,4370],{"class":1182}," RequestHandler",[539,4372,3000],{"class":545},[539,4374,3736],{"class":1197},[539,4376,1227],{"class":545},[539,4378,4379],{"class":568},".\u002F$types",[539,4381,1388],{"class":545},[539,4383,4384],{"class":541,"line":598},[539,4385,1309],{"emptyLinePlaceholder":1308},[539,4387,4388,4390,4393,4395,4397,4399,4401,4403,4406,4408,4411,4413],{"class":541,"line":615},[539,4389,3792],{"class":1197},[539,4391,4392],{"class":555}," const",[539,4394,3801],{"class":1182},[539,4396,562],{"class":545},[539,4398,4370],{"class":636},[539,4400,1194],{"class":545},[539,4402,3795],{"class":555},[539,4404,4405],{"class":545}," ({",[539,4407,3752],{"class":3172},[539,4409,4410],{"class":545}," })",[539,4412,3183],{"class":555},[539,4414,627],{"class":545},[539,4416,4417,4419,4421,4423,4425],{"class":541,"line":630},[539,4418,3811],{"class":555},[539,4420,1201],{"class":1182},[539,4422,1194],{"class":545},[539,4424,3731],{"class":1210},[539,4426,3835],{"class":1221},[539,4428,4429,4431,4433,4435,4437,4439],{"class":541,"line":652},[539,4430,3840],{"class":1182},[539,4432,523],{"class":545},[539,4434,3845],{"class":1182},[539,4436,523],{"class":545},[539,4438,3850],{"class":1182},[539,4440,3853],{"class":545},[539,4442,4443,4445,4447,4449,4451,4453],{"class":541,"line":686},[539,4444,3840],{"class":1182},[539,4446,523],{"class":545},[539,4448,3845],{"class":1182},[539,4450,523],{"class":545},[539,4452,3866],{"class":1182},[539,4454,3853],{"class":545},[539,4456,4457,4460,4462,4464,4466,4469,4471],{"class":541,"line":692},[539,4458,4459],{"class":545},"    {",[539,4461,3752],{"class":1221},[539,4463,562],{"class":545},[539,4465,1179],{"class":545},[539,4467,4468],{"class":1170}," \u002F* ... *\u002F",[539,4470,3000],{"class":545},[539,4472,4320],{"class":545},[539,4474,4475],{"class":541,"line":706},[539,4476,4042],{"class":1221},[539,4478,4479],{"class":541,"line":727},[539,4480,1309],{"emptyLinePlaceholder":1308},[539,4482,4483,4485,4487,4489,4491,4493,4495,4497,4499,4501,4503,4505,4507,4509,4511,4513],{"class":541,"line":747},[539,4484,3811],{"class":555},[539,4486,1179],{"class":545},[539,4488,1183],{"class":1221},[539,4490,562],{"class":545},[539,4492,1179],{"class":545},[539,4494,4061],{"class":1182},[539,4496,3000],{"class":545},[539,4498,3000],{"class":545},[539,4500,1194],{"class":545},[539,4502,1198],{"class":1197},[539,4504,1201],{"class":1182},[539,4506,523],{"class":545},[539,4508,493],{"class":1182},[539,4510,523],{"class":545},[539,4512,3060],{"class":1210},[539,4514,2475],{"class":1221},[539,4516,4517,4519,4521,4523,4525,4527,4530,4533,4536,4538,4540,4542,4544,4546,4548,4550,4552,4554,4556],{"class":541,"line":752},[539,4518,4086],{"class":1197},[539,4520,3193],{"class":1221},[539,4522,4091],{"class":545},[539,4524,3284],{"class":1182},[539,4526,3198],{"class":1221},[539,4528,4529],{"class":1197},"return",[539,4531,4532],{"class":545}," new",[539,4534,4535],{"class":1210}," Response",[539,4537,1214],{"class":1221},[539,4539,1232],{"class":545},[539,4541,4122],{"class":568},[539,4543,1232],{"class":545},[539,4545,673],{"class":545},[539,4547,1179],{"class":545},[539,4549,4132],{"class":1221},[539,4551,562],{"class":545},[539,4553,4137],{"class":827},[539,4555,3000],{"class":545},[539,4557,1303],{"class":1221},[539,4559,4560,4562,4564,4566,4568,4571,4573,4576,4578,4580,4582,4584],{"class":541,"line":773},[539,4561,4154],{"class":1197},[539,4563,4532],{"class":545},[539,4565,4535],{"class":1210},[539,4567,1214],{"class":1221},[539,4569,4570],{"class":1182},"JSON",[539,4572,523],{"class":545},[539,4574,4575],{"class":1210},"stringify",[539,4577,1214],{"class":1221},[539,4579,3956],{"class":545},[539,4581,4061],{"class":1182},[539,4583,3000],{"class":545},[539,4585,4586],{"class":1221},"))\n",[539,4588,4589],{"class":541,"line":794},[539,4590,851],{"class":545},[444,4592,4594],{"id":4593},"admin-operations-trên-server","Admin Operations trên Server",[449,4596,4598],{"className":1161,"code":4597,"language":1163,"meta":458,"style":458},"\u002F\u002F Dùng service role key cho admin operations (backend only)\nconst supabaseAdmin = createClient(url, serviceRoleKey)\n\n\u002F\u002F Verify user từ JWT gửi từ client\nconst { data: user, error } = await supabaseAdmin.auth.admin.getUser(jwt)\n\n\u002F\u002F Decode JWT không verify (chỉ đọc claims, không kiểm tra chữ ký)\nconst claims = supabaseAdmin.auth.getClaims(jwt)\n\u002F\u002F { sub: '...', email: '...', role: 'authenticated', ... }\n\n\u002F\u002F List tất cả users với pagination\nconst { data: { users, total } } = await supabaseAdmin.auth.admin.listUsers({\n  page: 1,\n  perPage: 100\n})\n",[456,4599,4600,4605,4626,4630,4635,4676,4680,4685,4707,4712,4716,4721,4768,4780,4790],{"__ignoreMap":458},[539,4601,4602],{"class":541,"line":542},[539,4603,4604],{"class":1170},"\u002F\u002F Dùng service role key cho admin operations (backend only)\n",[539,4606,4607,4609,4612,4615,4618,4621,4623],{"class":541,"line":549},[539,4608,1176],{"class":555},[539,4610,4611],{"class":1182}," supabaseAdmin ",[539,4613,4614],{"class":545},"=",[539,4616,4617],{"class":1210}," createClient",[539,4619,4620],{"class":1182},"(url",[539,4622,673],{"class":545},[539,4624,4625],{"class":1182}," serviceRoleKey)\n",[539,4627,4628],{"class":541,"line":577},[539,4629,1309],{"emptyLinePlaceholder":1308},[539,4631,4632],{"class":541,"line":598},[539,4633,4634],{"class":1170},"\u002F\u002F Verify user từ JWT gửi từ client\n",[539,4636,4637,4639,4641,4643,4645,4647,4649,4651,4653,4655,4657,4660,4662,4664,4666,4669,4671,4673],{"class":541,"line":615},[539,4638,1176],{"class":555},[539,4640,1179],{"class":545},[539,4642,1183],{"class":1221},[539,4644,562],{"class":545},[539,4646,4061],{"class":1182},[539,4648,673],{"class":545},[539,4650,1188],{"class":1182},[539,4652,1191],{"class":545},[539,4654,1194],{"class":545},[539,4656,1198],{"class":1197},[539,4658,4659],{"class":1182}," supabaseAdmin",[539,4661,523],{"class":545},[539,4663,493],{"class":1182},[539,4665,523],{"class":545},[539,4667,4668],{"class":1182},"admin",[539,4670,523],{"class":545},[539,4672,3060],{"class":1210},[539,4674,4675],{"class":1182},"(jwt)\n",[539,4677,4678],{"class":541,"line":630},[539,4679,1309],{"emptyLinePlaceholder":1308},[539,4681,4682],{"class":541,"line":652},[539,4683,4684],{"class":1170},"\u002F\u002F Decode JWT không verify (chỉ đọc claims, không kiểm tra chữ ký)\n",[539,4686,4687,4689,4692,4694,4696,4698,4700,4702,4705],{"class":541,"line":686},[539,4688,1176],{"class":555},[539,4690,4691],{"class":1182}," claims ",[539,4693,4614],{"class":545},[539,4695,4659],{"class":1182},[539,4697,523],{"class":545},[539,4699,493],{"class":1182},[539,4701,523],{"class":545},[539,4703,4704],{"class":1210},"getClaims",[539,4706,4675],{"class":1182},[539,4708,4709],{"class":541,"line":692},[539,4710,4711],{"class":1170},"\u002F\u002F { sub: '...', email: '...', role: 'authenticated', ... }\n",[539,4713,4714],{"class":541,"line":706},[539,4715,1309],{"emptyLinePlaceholder":1308},[539,4717,4718],{"class":541,"line":727},[539,4719,4720],{"class":1170},"\u002F\u002F List tất cả users với pagination\n",[539,4722,4723,4725,4727,4729,4731,4733,4736,4738,4741,4743,4745,4747,4749,4751,4753,4755,4757,4759,4761,4764,4766],{"class":541,"line":747},[539,4724,1176],{"class":555},[539,4726,1179],{"class":545},[539,4728,1183],{"class":1221},[539,4730,562],{"class":545},[539,4732,1179],{"class":545},[539,4734,4735],{"class":1182}," users",[539,4737,673],{"class":545},[539,4739,4740],{"class":1182}," total ",[539,4742,1191],{"class":545},[539,4744,3000],{"class":545},[539,4746,1194],{"class":545},[539,4748,1198],{"class":1197},[539,4750,4659],{"class":1182},[539,4752,523],{"class":545},[539,4754,493],{"class":1182},[539,4756,523],{"class":545},[539,4758,4668],{"class":1182},[539,4760,523],{"class":545},[539,4762,4763],{"class":1210},"listUsers",[539,4765,1214],{"class":1182},[539,4767,546],{"class":545},[539,4769,4770,4773,4775,4778],{"class":541,"line":752},[539,4771,4772],{"class":1221},"  page",[539,4774,562],{"class":545},[539,4776,4777],{"class":827}," 1",[539,4779,574],{"class":545},[539,4781,4782,4785,4787],{"class":541,"line":773},[539,4783,4784],{"class":1221},"  perPage",[539,4786,562],{"class":545},[539,4788,4789],{"class":827}," 100\n",[539,4791,4792,4794],{"class":541,"line":794},[539,4793,1191],{"class":545},[539,4795,1303],{"class":1182},[507,4797,4800,4803,4804,4807,4808,4811],{"color":4798,"icon":4799},"warning","i-lucide-shield-alert",[435,4801,4802],{},"Quan trọng:"," Service role key bypass RLS và có quyền admin toàn bộ. ",[435,4805,4806],{},"Tuyệt đối không expose service role key ra client."," Chỉ dùng trong Edge Functions, API routes server-side, hoặc backend. Kiểm tra ",[456,4809,4810],{},"Authorization"," header và verify JWT trước khi thực hiện admin operations.",[1146,4813],{},[427,4815,4817],{"id":4816},"multi-factor-authentication-mfa","Multi-Factor Authentication (MFA)",[431,4819,4820,4821,4824,4825,562],{},"Hỗ trợ MFA với ",[435,4822,4823],{},"TOTP (Time-based One-Time Password)"," và ",[435,4826,4827],{},"Phone",[449,4829,4831],{"className":1161,"code":4830,"language":1163,"meta":458,"style":458},"\u002F\u002F Bắt đầu enroll MFA (TOTP)\nconst { data, error } = await supabase.auth.mfa.enroll({\n  factorType: 'totp',\n  issuer: 'My App',\n  friendlyName: 'user@example.com'\n})\n\n\u002F\u002F Trả về QR code URL để user scan với Authenticator app\nconst { qr_code } = data.totp\n\n\u002F\u002F Challenge sau khi user đã enroll\nconst { data, error } = await supabase.auth.mfa.challenge({\n  factorId: factorId\n})\n\n\u002F\u002F Verify TOTP code\nconst { data, error } = await supabase.auth.mfa.verify({\n  factorId: factorId,\n  code: '123456', \u002F\u002F 6-digit code từ Authenticator app\n  challengeId: challengeId\n})\n\n\u002F\u002F Unenroll\nconst { data, error } = await supabase.auth.mfa.unenroll({\n  factorId: factorId\n})\n\n\u002F\u002F Lấy danh sách các factor đã enroll\nconst { data } = await supabase.auth.mfa.listFactors()\n",[456,4832,4833,4838,4876,4892,4908,4921,4927,4931,4936,4956,4960,4965,5002,5012,5018,5022,5027,5064,5075,5093,5103,5109,5113,5118,5155,5163,5169,5173,5178],{"__ignoreMap":458},[539,4834,4835],{"class":541,"line":542},[539,4836,4837],{"class":1170},"\u002F\u002F Bắt đầu enroll MFA (TOTP)\n",[539,4839,4840,4842,4844,4846,4848,4850,4852,4854,4856,4858,4860,4862,4864,4867,4869,4872,4874],{"class":541,"line":549},[539,4841,1176],{"class":555},[539,4843,1179],{"class":545},[539,4845,1183],{"class":1182},[539,4847,673],{"class":545},[539,4849,1188],{"class":1182},[539,4851,1191],{"class":545},[539,4853,1194],{"class":545},[539,4855,1198],{"class":1197},[539,4857,1201],{"class":1182},[539,4859,523],{"class":545},[539,4861,493],{"class":1182},[539,4863,523],{"class":545},[539,4865,4866],{"class":1182},"mfa",[539,4868,523],{"class":545},[539,4870,4871],{"class":1210},"enroll",[539,4873,1214],{"class":1182},[539,4875,546],{"class":545},[539,4877,4878,4881,4883,4885,4888,4890],{"class":541,"line":577},[539,4879,4880],{"class":1221},"  factorType",[539,4882,562],{"class":545},[539,4884,1227],{"class":545},[539,4886,4887],{"class":568},"totp",[539,4889,1232],{"class":545},[539,4891,574],{"class":545},[539,4893,4894,4897,4899,4901,4904,4906],{"class":541,"line":598},[539,4895,4896],{"class":1221},"  issuer",[539,4898,562],{"class":545},[539,4900,1227],{"class":545},[539,4902,4903],{"class":568},"My App",[539,4905,1232],{"class":545},[539,4907,574],{"class":545},[539,4909,4910,4913,4915,4917,4919],{"class":541,"line":615},[539,4911,4912],{"class":1221},"  friendlyName",[539,4914,562],{"class":545},[539,4916,1227],{"class":545},[539,4918,591],{"class":568},[539,4920,1388],{"class":545},[539,4922,4923,4925],{"class":541,"line":630},[539,4924,1191],{"class":545},[539,4926,1303],{"class":1182},[539,4928,4929],{"class":541,"line":652},[539,4930,1309],{"emptyLinePlaceholder":1308},[539,4932,4933],{"class":541,"line":686},[539,4934,4935],{"class":1170},"\u002F\u002F Trả về QR code URL để user scan với Authenticator app\n",[539,4937,4938,4940,4942,4945,4947,4949,4951,4953],{"class":541,"line":692},[539,4939,1176],{"class":555},[539,4941,1179],{"class":545},[539,4943,4944],{"class":1182}," qr_code ",[539,4946,1191],{"class":545},[539,4948,1194],{"class":545},[539,4950,1183],{"class":1182},[539,4952,523],{"class":545},[539,4954,4955],{"class":1182},"totp\n",[539,4957,4958],{"class":541,"line":706},[539,4959,1309],{"emptyLinePlaceholder":1308},[539,4961,4962],{"class":541,"line":727},[539,4963,4964],{"class":1170},"\u002F\u002F Challenge sau khi user đã enroll\n",[539,4966,4967,4969,4971,4973,4975,4977,4979,4981,4983,4985,4987,4989,4991,4993,4995,4998,5000],{"class":541,"line":747},[539,4968,1176],{"class":555},[539,4970,1179],{"class":545},[539,4972,1183],{"class":1182},[539,4974,673],{"class":545},[539,4976,1188],{"class":1182},[539,4978,1191],{"class":545},[539,4980,1194],{"class":545},[539,4982,1198],{"class":1197},[539,4984,1201],{"class":1182},[539,4986,523],{"class":545},[539,4988,493],{"class":1182},[539,4990,523],{"class":545},[539,4992,4866],{"class":1182},[539,4994,523],{"class":545},[539,4996,4997],{"class":1210},"challenge",[539,4999,1214],{"class":1182},[539,5001,546],{"class":545},[539,5003,5004,5007,5009],{"class":541,"line":752},[539,5005,5006],{"class":1221},"  factorId",[539,5008,562],{"class":545},[539,5010,5011],{"class":1182}," factorId\n",[539,5013,5014,5016],{"class":541,"line":773},[539,5015,1191],{"class":545},[539,5017,1303],{"class":1182},[539,5019,5020],{"class":541,"line":794},[539,5021,1309],{"emptyLinePlaceholder":1308},[539,5023,5024],{"class":541,"line":815},[539,5025,5026],{"class":1170},"\u002F\u002F Verify TOTP code\n",[539,5028,5029,5031,5033,5035,5037,5039,5041,5043,5045,5047,5049,5051,5053,5055,5057,5060,5062],{"class":541,"line":833},[539,5030,1176],{"class":555},[539,5032,1179],{"class":545},[539,5034,1183],{"class":1182},[539,5036,673],{"class":545},[539,5038,1188],{"class":1182},[539,5040,1191],{"class":545},[539,5042,1194],{"class":545},[539,5044,1198],{"class":1197},[539,5046,1201],{"class":1182},[539,5048,523],{"class":545},[539,5050,493],{"class":1182},[539,5052,523],{"class":545},[539,5054,4866],{"class":1182},[539,5056,523],{"class":545},[539,5058,5059],{"class":1210},"verify",[539,5061,1214],{"class":1182},[539,5063,546],{"class":545},[539,5065,5066,5068,5070,5073],{"class":541,"line":848},[539,5067,5006],{"class":1221},[539,5069,562],{"class":545},[539,5071,5072],{"class":1182}," factorId",[539,5074,574],{"class":545},[539,5076,5077,5080,5082,5084,5086,5088,5090],{"class":541,"line":1391},[539,5078,5079],{"class":1221},"  code",[539,5081,562],{"class":545},[539,5083,1227],{"class":545},[539,5085,1724],{"class":568},[539,5087,1232],{"class":545},[539,5089,673],{"class":545},[539,5091,5092],{"class":1170}," \u002F\u002F 6-digit code từ Authenticator app\n",[539,5094,5095,5098,5100],{"class":541,"line":1398},[539,5096,5097],{"class":1221},"  challengeId",[539,5099,562],{"class":545},[539,5101,5102],{"class":1182}," challengeId\n",[539,5104,5105,5107],{"class":541,"line":1403},[539,5106,1191],{"class":545},[539,5108,1303],{"class":1182},[539,5110,5111],{"class":541,"line":1409},[539,5112,1309],{"emptyLinePlaceholder":1308},[539,5114,5115],{"class":541,"line":2876},[539,5116,5117],{"class":1170},"\u002F\u002F Unenroll\n",[539,5119,5120,5122,5124,5126,5128,5130,5132,5134,5136,5138,5140,5142,5144,5146,5148,5151,5153],{"class":541,"line":3476},[539,5121,1176],{"class":555},[539,5123,1179],{"class":545},[539,5125,1183],{"class":1182},[539,5127,673],{"class":545},[539,5129,1188],{"class":1182},[539,5131,1191],{"class":545},[539,5133,1194],{"class":545},[539,5135,1198],{"class":1197},[539,5137,1201],{"class":1182},[539,5139,523],{"class":545},[539,5141,493],{"class":1182},[539,5143,523],{"class":545},[539,5145,4866],{"class":1182},[539,5147,523],{"class":545},[539,5149,5150],{"class":1210},"unenroll",[539,5152,1214],{"class":1182},[539,5154,546],{"class":545},[539,5156,5157,5159,5161],{"class":541,"line":3481},[539,5158,5006],{"class":1221},[539,5160,562],{"class":545},[539,5162,5011],{"class":1182},[539,5164,5165,5167],{"class":541,"line":3495},[539,5166,1191],{"class":545},[539,5168,1303],{"class":1182},[539,5170,5171],{"class":541,"line":3515},[539,5172,1309],{"emptyLinePlaceholder":1308},[539,5174,5175],{"class":541,"line":3520},[539,5176,5177],{"class":1170},"\u002F\u002F Lấy danh sách các factor đã enroll\n",[539,5179,5180,5182,5184,5186,5188,5190,5192,5194,5196,5198,5200,5202,5204,5207],{"class":541,"line":3525},[539,5181,1176],{"class":555},[539,5183,1179],{"class":545},[539,5185,2455],{"class":1182},[539,5187,1191],{"class":545},[539,5189,1194],{"class":545},[539,5191,1198],{"class":1197},[539,5193,1201],{"class":1182},[539,5195,523],{"class":545},[539,5197,493],{"class":1182},[539,5199,523],{"class":545},[539,5201,4866],{"class":1182},[539,5203,523],{"class":545},[539,5205,5206],{"class":1210},"listFactors",[539,5208,2475],{"class":1182},[444,5210,5212],{"id":5211},"mfa-flow","MFA Flow",[460,5214,5215,5218,5224,5233,5236,5242],{},[463,5216,5217],{},"User login với email\u002Fpassword",[463,5219,5220,5221,5223],{},"Server trả về ",[456,5222,787],{}," session (chưa MFA)",[463,5225,5226,5227,519,5230],{},"Client gọi ",[456,5228,5229],{},"mfa.challenge()",[456,5231,5232],{},"factorId",[463,5234,5235],{},"User nhập TOTP code từ Authenticator app",[463,5237,5226,5238,5241],{},[456,5239,5240],{},"mfa.verify()"," để xác nhận",[463,5243,5220,5244,5246],{},[456,5245,986],{}," session (đã MFA, full access)",[1146,5248],{},[427,5250,5252],{"id":5251},"auth-hooks","Auth Hooks",[431,5254,5255],{},"Customize hành vi Auth tại các điểm lifecycle bằng Postgres functions:",[858,5257,5258,5270],{},[861,5259,5260],{},[864,5261,5262,5265,5267],{},[867,5263,5264],{},"Hook",[867,5266,1080],{},[867,5268,5269],{},"Khi nào dùng",[877,5271,5272,5285,5298,5311],{},[864,5273,5274,5279,5282],{},[882,5275,5276],{},[435,5277,5278],{},"Custom Access Token",[882,5280,5281],{},"Thêm claims vào JWT access token",[882,5283,5284],{},"Thêm role, permissions, metadata vào JWT",[864,5286,5287,5292,5295],{},[882,5288,5289],{},[435,5290,5291],{},"Send SMS",[882,5293,5294],{},"Tùy chỉnh gửi SMS",[882,5296,5297],{},"Dùng provider SMS tùy chỉnh thay vì MessageBird\u002FTwilio",[864,5299,5300,5305,5308],{},[882,5301,5302],{},[435,5303,5304],{},"Send Email",[882,5306,5307],{},"Tùy chỉnh gửi email",[882,5309,5310],{},"Dùng Resend, SendGrid, custom SMTP thay vì Supabase default",[864,5312,5313,5318,5321],{},[882,5314,5315],{},[435,5316,5317],{},"MFA Verification Attempt",[882,5319,5320],{},"Custom logic khi verify MFA",[882,5322,5323],{},"Rate limiting, logging, custom validation",[449,5325,5329],{"className":5326,"code":5327,"language":5328,"meta":458,"style":458},"language-sql shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","-- Custom Access Token Hook: thêm custom claims vào JWT\n-- Hook này được gọi mỗi khi JWT được tạo hoặc refresh\nCREATE OR REPLACE FUNCTION public.custom_access_token_hook(event jsonb)\nRETURNS jsonb AS $$\nDECLARE\n  claims jsonb := event->'claims';\n  user_data jsonb;\nBEGIN\n  -- Lấy thông tin từ bảng profiles\n  SELECT jsonb_build_object(\n    'plan', plan,\n    'role', role,\n    'tenant_id', tenant_id\n  )\n  INTO user_data\n  FROM public.profiles\n  WHERE user_id = (event->>'user_id')::uuid;\n\n  -- Merge thông tin vào claims\n  -- user_metadata sẽ xuất hiện trong JWT và có thể dùng trong RLS policy\n  claims := claims || jsonb_build_object('user_metadata', user_data);\n\n  -- Set lại claims trong event\n  RETURN jsonb_set(event, '{claims}', claims);\nEND;\n$$ LANGUAGE plpgsql SECURITY DEFINER;\n\n-- Sau đó enable hook trong Dashboard > Authentication > Hooks\n-- Chọn \"Custom Access Token\" và chọn function public.custom_access_token_hook\n","sql",[456,5330,5331,5336,5341,5346,5351,5356,5361,5366,5371,5376,5381,5386,5391,5396,5400,5405,5410,5415,5419,5424,5429,5434,5438,5443,5448,5453,5458,5462,5467],{"__ignoreMap":458},[539,5332,5333],{"class":541,"line":542},[539,5334,5335],{},"-- Custom Access Token Hook: thêm custom claims vào JWT\n",[539,5337,5338],{"class":541,"line":549},[539,5339,5340],{},"-- Hook này được gọi mỗi khi JWT được tạo hoặc refresh\n",[539,5342,5343],{"class":541,"line":577},[539,5344,5345],{},"CREATE OR REPLACE FUNCTION public.custom_access_token_hook(event jsonb)\n",[539,5347,5348],{"class":541,"line":598},[539,5349,5350],{},"RETURNS jsonb AS $$\n",[539,5352,5353],{"class":541,"line":615},[539,5354,5355],{},"DECLARE\n",[539,5357,5358],{"class":541,"line":630},[539,5359,5360],{},"  claims jsonb := event->'claims';\n",[539,5362,5363],{"class":541,"line":652},[539,5364,5365],{},"  user_data jsonb;\n",[539,5367,5368],{"class":541,"line":686},[539,5369,5370],{},"BEGIN\n",[539,5372,5373],{"class":541,"line":692},[539,5374,5375],{},"  -- Lấy thông tin từ bảng profiles\n",[539,5377,5378],{"class":541,"line":706},[539,5379,5380],{},"  SELECT jsonb_build_object(\n",[539,5382,5383],{"class":541,"line":727},[539,5384,5385],{},"    'plan', plan,\n",[539,5387,5388],{"class":541,"line":747},[539,5389,5390],{},"    'role', role,\n",[539,5392,5393],{"class":541,"line":752},[539,5394,5395],{},"    'tenant_id', tenant_id\n",[539,5397,5398],{"class":541,"line":773},[539,5399,4042],{},[539,5401,5402],{"class":541,"line":794},[539,5403,5404],{},"  INTO user_data\n",[539,5406,5407],{"class":541,"line":815},[539,5408,5409],{},"  FROM public.profiles\n",[539,5411,5412],{"class":541,"line":833},[539,5413,5414],{},"  WHERE user_id = (event->>'user_id')::uuid;\n",[539,5416,5417],{"class":541,"line":848},[539,5418,1309],{"emptyLinePlaceholder":1308},[539,5420,5421],{"class":541,"line":1391},[539,5422,5423],{},"  -- Merge thông tin vào claims\n",[539,5425,5426],{"class":541,"line":1398},[539,5427,5428],{},"  -- user_metadata sẽ xuất hiện trong JWT và có thể dùng trong RLS policy\n",[539,5430,5431],{"class":541,"line":1403},[539,5432,5433],{},"  claims := claims || jsonb_build_object('user_metadata', user_data);\n",[539,5435,5436],{"class":541,"line":1409},[539,5437,1309],{"emptyLinePlaceholder":1308},[539,5439,5440],{"class":541,"line":2876},[539,5441,5442],{},"  -- Set lại claims trong event\n",[539,5444,5445],{"class":541,"line":3476},[539,5446,5447],{},"  RETURN jsonb_set(event, '{claims}', claims);\n",[539,5449,5450],{"class":541,"line":3481},[539,5451,5452],{},"END;\n",[539,5454,5455],{"class":541,"line":3495},[539,5456,5457],{},"$$ LANGUAGE plpgsql SECURITY DEFINER;\n",[539,5459,5460],{"class":541,"line":3515},[539,5461,1309],{"emptyLinePlaceholder":1308},[539,5463,5464],{"class":541,"line":3520},[539,5465,5466],{},"-- Sau đó enable hook trong Dashboard > Authentication > Hooks\n",[539,5468,5469],{"class":541,"line":3525},[539,5470,5471],{},"-- Chọn \"Custom Access Token\" và chọn function public.custom_access_token_hook\n",[449,5473,5475],{"className":5326,"code":5474,"language":5328,"meta":458,"style":458},"-- Send Email Hook: tùy chỉnh gửi email qua Resend\u002FSendGrid\nCREATE OR REPLACE FUNCTION public.custom_send_email_hook(event jsonb)\nRETURNS jsonb AS $$\nDECLARE\n  response jsonb;\nBEGIN\n  -- Gọi external email API (Resend, SendGrid, v.v.)\n  SELECT content::jsonb INTO response\n  FROM http((\n    'POST',\n    'https:\u002F\u002Fapi.resend.com\u002Femails',\n    ARRAY[http_header('Authorization', 'Bearer re_xxxxx')],\n    'application\u002Fjson',\n    jsonb_build_object(\n      'from', 'My App \u003Cnoreply@myapp.com>',\n      'to', ARRAY[event->>'email'],\n      'subject', event->>'email_action_type',\n      'html', event->>'redirect_to'\n    )::text\n  ));\n\n  RETURN event;\nEND;\n$$ LANGUAGE plpgsql;\n",[456,5476,5477,5482,5487,5491,5495,5500,5504,5509,5514,5519,5524,5529,5534,5539,5544,5549,5554,5559,5564,5569,5574,5578,5583,5587],{"__ignoreMap":458},[539,5478,5479],{"class":541,"line":542},[539,5480,5481],{},"-- Send Email Hook: tùy chỉnh gửi email qua Resend\u002FSendGrid\n",[539,5483,5484],{"class":541,"line":549},[539,5485,5486],{},"CREATE OR REPLACE FUNCTION public.custom_send_email_hook(event jsonb)\n",[539,5488,5489],{"class":541,"line":577},[539,5490,5350],{},[539,5492,5493],{"class":541,"line":598},[539,5494,5355],{},[539,5496,5497],{"class":541,"line":615},[539,5498,5499],{},"  response jsonb;\n",[539,5501,5502],{"class":541,"line":630},[539,5503,5370],{},[539,5505,5506],{"class":541,"line":652},[539,5507,5508],{},"  -- Gọi external email API (Resend, SendGrid, v.v.)\n",[539,5510,5511],{"class":541,"line":686},[539,5512,5513],{},"  SELECT content::jsonb INTO response\n",[539,5515,5516],{"class":541,"line":692},[539,5517,5518],{},"  FROM http((\n",[539,5520,5521],{"class":541,"line":706},[539,5522,5523],{},"    'POST',\n",[539,5525,5526],{"class":541,"line":727},[539,5527,5528],{},"    'https:\u002F\u002Fapi.resend.com\u002Femails',\n",[539,5530,5531],{"class":541,"line":747},[539,5532,5533],{},"    ARRAY[http_header('Authorization', 'Bearer re_xxxxx')],\n",[539,5535,5536],{"class":541,"line":752},[539,5537,5538],{},"    'application\u002Fjson',\n",[539,5540,5541],{"class":541,"line":773},[539,5542,5543],{},"    jsonb_build_object(\n",[539,5545,5546],{"class":541,"line":794},[539,5547,5548],{},"      'from', 'My App \u003Cnoreply@myapp.com>',\n",[539,5550,5551],{"class":541,"line":815},[539,5552,5553],{},"      'to', ARRAY[event->>'email'],\n",[539,5555,5556],{"class":541,"line":833},[539,5557,5558],{},"      'subject', event->>'email_action_type',\n",[539,5560,5561],{"class":541,"line":848},[539,5562,5563],{},"      'html', event->>'redirect_to'\n",[539,5565,5566],{"class":541,"line":1391},[539,5567,5568],{},"    )::text\n",[539,5570,5571],{"class":541,"line":1398},[539,5572,5573],{},"  ));\n",[539,5575,5576],{"class":541,"line":1403},[539,5577,1309],{"emptyLinePlaceholder":1308},[539,5579,5580],{"class":541,"line":1409},[539,5581,5582],{},"  RETURN event;\n",[539,5584,5585],{"class":541,"line":2876},[539,5586,5452],{},[539,5588,5589],{"class":541,"line":3476},[539,5590,5591],{},"$$ LANGUAGE plpgsql;\n",[507,5593,5594,5597,5598,498,5601,498,5603,5606,5607,5610,5611,5614],{"color":1036,"icon":1037},[435,5595,5596],{},"Interview point — Custom Access Token Hook:"," Đây là cách để thêm custom claims (như ",[456,5599,5600],{},"plan",[456,5602,757],{},[456,5604,5605],{},"tenant_id",") vào JWT mà không cần tự tạo JWT server riêng. Claims được thêm vào sẽ có sẵn trong RLS policies qua ",[456,5608,5609],{},"auth.jwt()",". Hook chạy trong Postgres với ",[456,5612,5613],{},"SECURITY DEFINER"," để có quyền đọc bảng profiles.",[1146,5616],{},[427,5618,5620],{"id":5619},"auth-ui-components","Auth UI Components",[431,5622,5623],{},"Supabase cung cấp pre-built UI components:",[449,5625,5629],{"className":5626,"code":5627,"language":5628,"meta":458,"style":458},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","npm install @supabase\u002Fsupabase-js @supabase\u002Fssr\n\n# Auth UI (React)\nnpm install @supabase\u002Fauth-ui-react @supabase\u002Fauth-ui-shared\n\n# Auth UI (Vue)\nnpm install @supabase\u002Fauth-ui-vue\n","bash",[456,5630,5631,5645,5649,5654,5666,5670,5675],{"__ignoreMap":458},[539,5632,5633,5636,5639,5642],{"class":541,"line":542},[539,5634,5635],{"class":636},"npm",[539,5637,5638],{"class":568}," install",[539,5640,5641],{"class":568}," @supabase\u002Fsupabase-js",[539,5643,5644],{"class":568}," @supabase\u002Fssr\n",[539,5646,5647],{"class":541,"line":549},[539,5648,1309],{"emptyLinePlaceholder":1308},[539,5650,5651],{"class":541,"line":577},[539,5652,5653],{"class":1170},"# Auth UI (React)\n",[539,5655,5656,5658,5660,5663],{"class":541,"line":598},[539,5657,5635],{"class":636},[539,5659,5638],{"class":568},[539,5661,5662],{"class":568}," @supabase\u002Fauth-ui-react",[539,5664,5665],{"class":568}," @supabase\u002Fauth-ui-shared\n",[539,5667,5668],{"class":541,"line":615},[539,5669,1309],{"emptyLinePlaceholder":1308},[539,5671,5672],{"class":541,"line":630},[539,5673,5674],{"class":1170},"# Auth UI (Vue)\n",[539,5676,5677,5679,5681],{"class":541,"line":652},[539,5678,5635],{"class":636},[539,5680,5638],{"class":568},[539,5682,5683],{"class":568}," @supabase\u002Fauth-ui-vue\n",[449,5685,5689],{"className":5686,"code":5687,"language":5688,"meta":458,"style":458},"language-vue shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003C!-- Vue example -->\n\u003Cscript setup>\nimport { Auth } from '@supabase\u002Fauth-ui-vue'\nimport { createClient } from '@supabase\u002Fsupabase-js'\n\nconst supabase = createClient(url, anonKey)\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CAuth :supabase-client=\"supabase\" \u002F>\n\u003C\u002Ftemplate>\n","vue",[456,5690,5691,5696,5710,5730,5748,5752,5770,5779,5783,5792,5814],{"__ignoreMap":458},[539,5692,5693],{"class":541,"line":542},[539,5694,5695],{"class":1170},"\u003C!-- Vue example -->\n",[539,5697,5698,5701,5704,5707],{"class":541,"line":549},[539,5699,5700],{"class":545},"\u003C",[539,5702,5703],{"class":1221},"script",[539,5705,5706],{"class":555}," setup",[539,5708,5709],{"class":545},">\n",[539,5711,5712,5714,5716,5719,5721,5723,5725,5728],{"class":541,"line":577},[539,5713,3726],{"class":1197},[539,5715,1179],{"class":545},[539,5717,5718],{"class":1182}," Auth",[539,5720,3000],{"class":545},[539,5722,3736],{"class":1197},[539,5724,1227],{"class":545},[539,5726,5727],{"class":568},"@supabase\u002Fauth-ui-vue",[539,5729,1388],{"class":545},[539,5731,5732,5734,5736,5738,5740,5742,5744,5746],{"class":541,"line":598},[539,5733,3726],{"class":1197},[539,5735,1179],{"class":545},[539,5737,4617],{"class":1182},[539,5739,3000],{"class":545},[539,5741,3736],{"class":1197},[539,5743,1227],{"class":545},[539,5745,471],{"class":568},[539,5747,1388],{"class":545},[539,5749,5750],{"class":541,"line":615},[539,5751,1309],{"emptyLinePlaceholder":1308},[539,5753,5754,5756,5759,5761,5763,5765,5767],{"class":541,"line":630},[539,5755,1176],{"class":555},[539,5757,5758],{"class":1182}," supabase ",[539,5760,4614],{"class":545},[539,5762,4617],{"class":1210},[539,5764,4620],{"class":1182},[539,5766,673],{"class":545},[539,5768,5769],{"class":1182}," anonKey)\n",[539,5771,5772,5775,5777],{"class":541,"line":652},[539,5773,5774],{"class":545},"\u003C\u002F",[539,5776,5703],{"class":1221},[539,5778,5709],{"class":545},[539,5780,5781],{"class":541,"line":686},[539,5782,1309],{"emptyLinePlaceholder":1308},[539,5784,5785,5787,5790],{"class":541,"line":692},[539,5786,5700],{"class":545},[539,5788,5789],{"class":1221},"template",[539,5791,5709],{"class":545},[539,5793,5794,5797,5800,5803,5805,5807,5809,5811],{"class":541,"line":706},[539,5795,5796],{"class":545},"  \u003C",[539,5798,5799],{"class":1221},"Auth",[539,5801,5802],{"class":555}," :supabase-client",[539,5804,4614],{"class":545},[539,5806,559],{"class":545},[539,5808,3157],{"class":568},[539,5810,559],{"class":545},[539,5812,5813],{"class":545}," \u002F>\n",[539,5815,5816,5818,5820],{"class":541,"line":727},[539,5817,5774],{"class":545},[539,5819,5789],{"class":1221},[539,5821,5709],{"class":545},[431,5823,5824,5825,5828],{},"Component ",[456,5826,5827],{},"\u003CAuth \u002F>"," tự động bao gồm: Sign Up, Sign In, Magic Link, OAuth buttons, password reset, update password.",[1146,5830],{},[427,5832,5834],{"id":5833},"user-management-admin","User Management (Admin)",[431,5836,5837],{},"Dùng service role key để quản lý users từ backend:",[449,5839,5841],{"className":1161,"code":5840,"language":1163,"meta":458,"style":458},"\u002F\u002F Khởi tạo admin client (backend only)\nconst supabaseAdmin = createClient(url, serviceRoleKey)\n\n\u002F\u002F Lấy user theo ID\nconst { data, error } = await supabaseAdmin.auth.admin.getUserById(userId)\n\n\u002F\u002F Liệt kê tất cả users\nconst { data, error } = await supabaseAdmin.auth.admin.listUsers({\n  page: 1,\n  perPage: 100\n})\n\n\u002F\u002F Tạo user (không gửi email xác nhận)\nconst { data, error } = await supabaseAdmin.auth.admin.createUser({\n  email: 'user@example.com',\n  password: 'password123',\n  email_confirm: true, \u002F\u002F Bypass email verification\n  user_metadata: { full_name: 'Nguyen Van A' }\n})\n\n\u002F\u002F Cập nhật user\nconst { data, error } = await supabaseAdmin.auth.admin.updateUserById(userId, {\n  email: 'newemail@example.com',\n  user_metadata: { role: 'admin' }\n})\n\n\u002F\u002F Xóa user\nconst { data, error } = await supabaseAdmin.auth.admin.deleteUser(userId)\n",[456,5842,5843,5848,5864,5868,5873,5909,5913,5918,5954,5964,5972,5978,5982,5987,6024,6038,6053,6067,6089,6095,6099,6104,6144,6159,6180,6186,6190,6195],{"__ignoreMap":458},[539,5844,5845],{"class":541,"line":542},[539,5846,5847],{"class":1170},"\u002F\u002F Khởi tạo admin client (backend only)\n",[539,5849,5850,5852,5854,5856,5858,5860,5862],{"class":541,"line":549},[539,5851,1176],{"class":555},[539,5853,4611],{"class":1182},[539,5855,4614],{"class":545},[539,5857,4617],{"class":1210},[539,5859,4620],{"class":1182},[539,5861,673],{"class":545},[539,5863,4625],{"class":1182},[539,5865,5866],{"class":541,"line":577},[539,5867,1309],{"emptyLinePlaceholder":1308},[539,5869,5870],{"class":541,"line":598},[539,5871,5872],{"class":1170},"\u002F\u002F Lấy user theo ID\n",[539,5874,5875,5877,5879,5881,5883,5885,5887,5889,5891,5893,5895,5897,5899,5901,5903,5906],{"class":541,"line":615},[539,5876,1176],{"class":555},[539,5878,1179],{"class":545},[539,5880,1183],{"class":1182},[539,5882,673],{"class":545},[539,5884,1188],{"class":1182},[539,5886,1191],{"class":545},[539,5888,1194],{"class":545},[539,5890,1198],{"class":1197},[539,5892,4659],{"class":1182},[539,5894,523],{"class":545},[539,5896,493],{"class":1182},[539,5898,523],{"class":545},[539,5900,4668],{"class":1182},[539,5902,523],{"class":545},[539,5904,5905],{"class":1210},"getUserById",[539,5907,5908],{"class":1182},"(userId)\n",[539,5910,5911],{"class":541,"line":630},[539,5912,1309],{"emptyLinePlaceholder":1308},[539,5914,5915],{"class":541,"line":652},[539,5916,5917],{"class":1170},"\u002F\u002F Liệt kê tất cả users\n",[539,5919,5920,5922,5924,5926,5928,5930,5932,5934,5936,5938,5940,5942,5944,5946,5948,5950,5952],{"class":541,"line":686},[539,5921,1176],{"class":555},[539,5923,1179],{"class":545},[539,5925,1183],{"class":1182},[539,5927,673],{"class":545},[539,5929,1188],{"class":1182},[539,5931,1191],{"class":545},[539,5933,1194],{"class":545},[539,5935,1198],{"class":1197},[539,5937,4659],{"class":1182},[539,5939,523],{"class":545},[539,5941,493],{"class":1182},[539,5943,523],{"class":545},[539,5945,4668],{"class":1182},[539,5947,523],{"class":545},[539,5949,4763],{"class":1210},[539,5951,1214],{"class":1182},[539,5953,546],{"class":545},[539,5955,5956,5958,5960,5962],{"class":541,"line":692},[539,5957,4772],{"class":1221},[539,5959,562],{"class":545},[539,5961,4777],{"class":827},[539,5963,574],{"class":545},[539,5965,5966,5968,5970],{"class":541,"line":706},[539,5967,4784],{"class":1221},[539,5969,562],{"class":545},[539,5971,4789],{"class":827},[539,5973,5974,5976],{"class":541,"line":727},[539,5975,1191],{"class":545},[539,5977,1303],{"class":1182},[539,5979,5980],{"class":541,"line":747},[539,5981,1309],{"emptyLinePlaceholder":1308},[539,5983,5984],{"class":541,"line":752},[539,5985,5986],{"class":1170},"\u002F\u002F Tạo user (không gửi email xác nhận)\n",[539,5988,5989,5991,5993,5995,5997,5999,6001,6003,6005,6007,6009,6011,6013,6015,6017,6020,6022],{"class":541,"line":773},[539,5990,1176],{"class":555},[539,5992,1179],{"class":545},[539,5994,1183],{"class":1182},[539,5996,673],{"class":545},[539,5998,1188],{"class":1182},[539,6000,1191],{"class":545},[539,6002,1194],{"class":545},[539,6004,1198],{"class":1197},[539,6006,4659],{"class":1182},[539,6008,523],{"class":545},[539,6010,493],{"class":1182},[539,6012,523],{"class":545},[539,6014,4668],{"class":1182},[539,6016,523],{"class":545},[539,6018,6019],{"class":1210},"createUser",[539,6021,1214],{"class":1182},[539,6023,546],{"class":545},[539,6025,6026,6028,6030,6032,6034,6036],{"class":541,"line":794},[539,6027,1222],{"class":1221},[539,6029,562],{"class":545},[539,6031,1227],{"class":545},[539,6033,591],{"class":568},[539,6035,1232],{"class":545},[539,6037,574],{"class":545},[539,6039,6040,6042,6044,6046,6049,6051],{"class":541,"line":815},[539,6041,1239],{"class":1221},[539,6043,562],{"class":545},[539,6045,1227],{"class":545},[539,6047,6048],{"class":568},"password123",[539,6050,1232],{"class":545},[539,6052,574],{"class":545},[539,6054,6055,6058,6060,6062,6064],{"class":541,"line":833},[539,6056,6057],{"class":1221},"  email_confirm",[539,6059,562],{"class":545},[539,6061,1512],{"class":1511},[539,6063,673],{"class":545},[539,6065,6066],{"class":1170}," \u002F\u002F Bypass email verification\n",[539,6068,6069,6072,6074,6076,6079,6081,6083,6085,6087],{"class":541,"line":848},[539,6070,6071],{"class":1221},"  user_metadata",[539,6073,562],{"class":545},[539,6075,1179],{"class":545},[539,6077,6078],{"class":1221}," full_name",[539,6080,562],{"class":545},[539,6082,1227],{"class":545},[539,6084,720],{"class":568},[539,6086,1232],{"class":545},[539,6088,4320],{"class":545},[539,6090,6091,6093],{"class":541,"line":1391},[539,6092,1191],{"class":545},[539,6094,1303],{"class":1182},[539,6096,6097],{"class":541,"line":1398},[539,6098,1309],{"emptyLinePlaceholder":1308},[539,6100,6101],{"class":541,"line":1403},[539,6102,6103],{"class":1170},"\u002F\u002F Cập nhật user\n",[539,6105,6106,6108,6110,6112,6114,6116,6118,6120,6122,6124,6126,6128,6130,6132,6134,6137,6140,6142],{"class":541,"line":1409},[539,6107,1176],{"class":555},[539,6109,1179],{"class":545},[539,6111,1183],{"class":1182},[539,6113,673],{"class":545},[539,6115,1188],{"class":1182},[539,6117,1191],{"class":545},[539,6119,1194],{"class":545},[539,6121,1198],{"class":1197},[539,6123,4659],{"class":1182},[539,6125,523],{"class":545},[539,6127,493],{"class":1182},[539,6129,523],{"class":545},[539,6131,4668],{"class":1182},[539,6133,523],{"class":545},[539,6135,6136],{"class":1210},"updateUserById",[539,6138,6139],{"class":1182},"(userId",[539,6141,673],{"class":545},[539,6143,627],{"class":545},[539,6145,6146,6148,6150,6152,6155,6157],{"class":541,"line":2876},[539,6147,1222],{"class":1221},[539,6149,562],{"class":545},[539,6151,1227],{"class":545},[539,6153,6154],{"class":568},"newemail@example.com",[539,6156,1232],{"class":545},[539,6158,574],{"class":545},[539,6160,6161,6163,6165,6167,6170,6172,6174,6176,6178],{"class":541,"line":3476},[539,6162,6071],{"class":1221},[539,6164,562],{"class":545},[539,6166,1179],{"class":545},[539,6168,6169],{"class":1221}," role",[539,6171,562],{"class":545},[539,6173,1227],{"class":545},[539,6175,4668],{"class":568},[539,6177,1232],{"class":545},[539,6179,4320],{"class":545},[539,6181,6182,6184],{"class":541,"line":3481},[539,6183,1191],{"class":545},[539,6185,1303],{"class":1182},[539,6187,6188],{"class":541,"line":3495},[539,6189,1309],{"emptyLinePlaceholder":1308},[539,6191,6192],{"class":541,"line":3515},[539,6193,6194],{"class":1170},"\u002F\u002F Xóa user\n",[539,6196,6197,6199,6201,6203,6205,6207,6209,6211,6213,6215,6217,6219,6221,6223,6225,6228],{"class":541,"line":3520},[539,6198,1176],{"class":555},[539,6200,1179],{"class":545},[539,6202,1183],{"class":1182},[539,6204,673],{"class":545},[539,6206,1188],{"class":1182},[539,6208,1191],{"class":545},[539,6210,1194],{"class":545},[539,6212,1198],{"class":1197},[539,6214,4659],{"class":1182},[539,6216,523],{"class":545},[539,6218,493],{"class":1182},[539,6220,523],{"class":545},[539,6222,4668],{"class":1182},[539,6224,523],{"class":545},[539,6226,6227],{"class":1210},"deleteUser",[539,6229,5908],{"class":1182},[1146,6231],{},[427,6233,6235],{"id":6234},"pricing-auth","Pricing Auth",[431,6237,6238,6239,562],{},"Tính phí theo ",[435,6240,6241],{},"Monthly Active Users (MAU)",[858,6243,6244,6257],{},[861,6245,6246],{},[864,6247,6248,6251,6254],{},[867,6249,6250],{},"Plan",[867,6252,6253],{},"MAU",[867,6255,6256],{},"Giá vượt",[877,6258,6259,6270],{},[864,6260,6261,6264,6267],{},[882,6262,6263],{},"Free",[882,6265,6266],{},"50,000 MAU",[882,6268,6269],{},"—",[864,6271,6272,6275,6278],{},[882,6273,6274],{},"Pro",[882,6276,6277],{},"100,000 MAU",[882,6279,6280],{},"$0.00325\u002FMAU",[1546,6282,6283,6289,6295],{},[463,6284,6285,6288],{},[435,6286,6287],{},"Third-Party MAU"," (OAuth, phone auth): tính phí riêng",[463,6290,6291,6294],{},[435,6292,6293],{},"SSO MAU"," (SAML): tính phí riêng",[463,6296,6297,6300],{},[435,6298,6299],{},"Advanced MFA (Phone)",": add-on tính phí",[1146,6302],{},[427,6304,6306],{"id":6305},"password-security-chi-tiết","Password Security — Chi tiết",[444,6308,6310],{"id":6309},"rate-limits","Rate Limits",[431,6312,6313],{},"Supabase áp dụng rate limiting để chống brute-force attacks:",[858,6315,6316,6329],{},[861,6317,6318],{},[864,6319,6320,6323,6326],{},[867,6321,6322],{},"Hành động",[867,6324,6325],{},"Giới hạn",[867,6327,6328],{},"Time window",[877,6330,6331,6345,6357,6370,6381,6394],{},[864,6332,6333,6339,6342],{},[882,6334,6335,6338],{},[435,6336,6337],{},"Login attempts"," (email\u002Fpassword)",[882,6340,6341],{},"5 lần",[882,6343,6344],{},"Mỗi phút, mỗi email",[864,6346,6347,6352,6354],{},[882,6348,6349],{},[435,6350,6351],{},"Signup attempts",[882,6353,6341],{},[882,6355,6356],{},"Mỗi giờ, mỗi email",[864,6358,6359,6365,6367],{},[882,6360,6361,6364],{},[435,6362,6363],{},"OTP requests"," (email\u002Fphone)",[882,6366,6341],{},[882,6368,6369],{},"Mỗi phút, mỗi email\u002Fphone",[864,6371,6372,6377,6379],{},[882,6373,6374],{},[435,6375,6376],{},"Password reset requests",[882,6378,6341],{},[882,6380,6356],{},[864,6382,6383,6388,6391],{},[882,6384,6385],{},[435,6386,6387],{},"Token refresh",[882,6389,6390],{},"Không giới hạn cứng",[882,6392,6393],{},"Rate limit nhẹ",[864,6395,6396,6401,6404],{},[882,6397,6398],{},[435,6399,6400],{},"Overall auth requests",[882,6402,6403],{},"30 req\u002Fs",[882,6405,6406],{},"Mỗi IP",[507,6408,6409,6410,6413,6414,6417],{"color":4798,"icon":4799},"Các rate limit này là ",[435,6411,6412],{},"hard limits"," trên server — không thể bypass bằng client code. Khi vượt quá, API trả về ",[456,6415,6416],{},"429 Too Many Requests",". Đối với Pro\u002FTeam plan, có thể liên hệ support để điều chỉnh.",[444,6419,6421],{"id":6420},"password-requirements","Password Requirements",[1546,6423,6424,6430,6436,6443,6450],{},[463,6425,6426,6429],{},[435,6427,6428],{},"Độ dài tối thiểu:"," 6 ký tự (mặc định)",[463,6431,6432,6435],{},[435,6433,6434],{},"Không có yêu cầu"," về uppercase, lowercase, số, hoặc ký tự đặc biệt theo mặc định",[463,6437,6438,6439,6442],{},"Có thể tăng requirements qua ",[435,6440,6441],{},"Custom Auth Hook"," hoặc validate ở client trước khi gửi",[463,6444,6445,6446,6449],{},"Mật khẩu được hash bằng ",[435,6447,6448],{},"bcrypt\u002Fscrypt"," (không lưu plaintext)",[463,6451,6452],{},"Có thể cấu hình password policy chi tiết hơn trong Dashboard > Authentication > Settings",[444,6454,6456],{"id":6455},"bot-detection-captcha","Bot Detection (CAPTCHA)",[858,6458,6459,6471],{},[861,6460,6461],{},[864,6462,6463,6466,6468],{},[867,6464,6465],{},"CAPTCHA Provider",[867,6467,1764],{},[867,6469,6470],{},"Cấu hình trong Dashboard",[877,6472,6473,6486],{},[864,6474,6475,6480,6483],{},[882,6476,6477],{},[435,6478,6479],{},"hCaptcha",[882,6481,6482],{},"Cần site key + secret key, hoạt động như reCAPTCHA",[882,6484,6485],{},"Authentication > Settings > CAPTCHA",[864,6487,6488,6493,6496],{},[882,6489,6490],{},[435,6491,6492],{},"Cloudflare Turnstile",[882,6494,6495],{},"Miễn phí, bảo vệ privacy, không tracking user",[882,6497,6485],{},[449,6499,6501],{"className":1161,"code":6500,"language":1163,"meta":458,"style":458},"\u002F\u002F Khi enable CAPTCHA, client SDK tự động lấy token\nconst { data, error } = await supabase.auth.signUp({\n  email: 'user@example.com',\n  password: 'password123',\n  options: {\n    captchaToken: token \u002F\u002F Từ Turnstile\u002FhCaptcha widget\n  }\n})\n",[456,6502,6503,6508,6540,6554,6568,6576,6589,6593],{"__ignoreMap":458},[539,6504,6505],{"class":541,"line":542},[539,6506,6507],{"class":1170},"\u002F\u002F Khi enable CAPTCHA, client SDK tự động lấy token\n",[539,6509,6510,6512,6514,6516,6518,6520,6522,6524,6526,6528,6530,6532,6534,6536,6538],{"class":541,"line":549},[539,6511,1176],{"class":555},[539,6513,1179],{"class":545},[539,6515,1183],{"class":1182},[539,6517,673],{"class":545},[539,6519,1188],{"class":1182},[539,6521,1191],{"class":545},[539,6523,1194],{"class":545},[539,6525,1198],{"class":1197},[539,6527,1201],{"class":1182},[539,6529,523],{"class":545},[539,6531,493],{"class":1182},[539,6533,523],{"class":545},[539,6535,1211],{"class":1210},[539,6537,1214],{"class":1182},[539,6539,546],{"class":545},[539,6541,6542,6544,6546,6548,6550,6552],{"class":541,"line":577},[539,6543,1222],{"class":1221},[539,6545,562],{"class":545},[539,6547,1227],{"class":545},[539,6549,591],{"class":568},[539,6551,1232],{"class":545},[539,6553,574],{"class":545},[539,6555,6556,6558,6560,6562,6564,6566],{"class":541,"line":598},[539,6557,1239],{"class":1221},[539,6559,562],{"class":545},[539,6561,1227],{"class":545},[539,6563,6048],{"class":568},[539,6565,1232],{"class":545},[539,6567,574],{"class":545},[539,6569,6570,6572,6574],{"class":541,"line":615},[539,6571,1255],{"class":1221},[539,6573,562],{"class":545},[539,6575,627],{"class":545},[539,6577,6578,6581,6583,6586],{"class":541,"line":630},[539,6579,6580],{"class":1221},"    captchaToken",[539,6582,562],{"class":545},[539,6584,6585],{"class":1182}," token ",[539,6587,6588],{"class":1170},"\u002F\u002F Từ Turnstile\u002FhCaptcha widget\n",[539,6590,6591],{"class":541,"line":652},[539,6592,1296],{"class":545},[539,6594,6595,6597],{"class":541,"line":686},[539,6596,1191],{"class":545},[539,6598,1303],{"class":1182},[507,6600,6601],{"color":509,"icon":510},"CAPTCHA được tự động kích hoạt khi Supabase phát hiện dấu hiệu bot dựa trên heuristics. Bạn cũng có thể bắt buộc CAPTCHA cho tất cả sign up để tăng bảo mật.",[444,6603,6605],{"id":6604},"audit-logs","Audit Logs",[449,6607,6609],{"className":5326,"code":6608,"language":5328,"meta":458,"style":458},"-- Xem audit logs (Team plan trở lên)\nSELECT\n  created_at,\n  ip_address,\n  payload->>'action' as action,\n  payload->>'actor_username' as user_email,\n  payload->>'traits' as details\nFROM auth.audit_log_entries\nWHERE created_at > now() - interval '7 days'\nORDER BY created_at DESC\nLIMIT 100;\n\n-- Các action được log:\n-- 'user_signedup', 'user_confirmed', 'user_repeated_signup',\n-- 'login', 'token_refreshed', 'user_modified',\n-- 'user_deleted', 'user_recovery_requested'\n",[456,6610,6611,6616,6621,6626,6631,6636,6641,6646,6651,6656,6661,6666,6670,6675,6680,6685],{"__ignoreMap":458},[539,6612,6613],{"class":541,"line":542},[539,6614,6615],{},"-- Xem audit logs (Team plan trở lên)\n",[539,6617,6618],{"class":541,"line":549},[539,6619,6620],{},"SELECT\n",[539,6622,6623],{"class":541,"line":577},[539,6624,6625],{},"  created_at,\n",[539,6627,6628],{"class":541,"line":598},[539,6629,6630],{},"  ip_address,\n",[539,6632,6633],{"class":541,"line":615},[539,6634,6635],{},"  payload->>'action' as action,\n",[539,6637,6638],{"class":541,"line":630},[539,6639,6640],{},"  payload->>'actor_username' as user_email,\n",[539,6642,6643],{"class":541,"line":652},[539,6644,6645],{},"  payload->>'traits' as details\n",[539,6647,6648],{"class":541,"line":686},[539,6649,6650],{},"FROM auth.audit_log_entries\n",[539,6652,6653],{"class":541,"line":692},[539,6654,6655],{},"WHERE created_at > now() - interval '7 days'\n",[539,6657,6658],{"class":541,"line":706},[539,6659,6660],{},"ORDER BY created_at DESC\n",[539,6662,6663],{"class":541,"line":727},[539,6664,6665],{},"LIMIT 100;\n",[539,6667,6668],{"class":541,"line":747},[539,6669,1309],{"emptyLinePlaceholder":1308},[539,6671,6672],{"class":541,"line":752},[539,6673,6674],{},"-- Các action được log:\n",[539,6676,6677],{"class":541,"line":773},[539,6678,6679],{},"-- 'user_signedup', 'user_confirmed', 'user_repeated_signup',\n",[539,6681,6682],{"class":541,"line":794},[539,6683,6684],{},"-- 'login', 'token_refreshed', 'user_modified',\n",[539,6686,6687],{"class":541,"line":815},[539,6688,6689],{},"-- 'user_deleted', 'user_recovery_requested'\n",[1146,6691],{},[427,6693,6695],{"id":6694},"interview-callouts","⭐ Interview Callouts",[507,6697,6698,6701,6702,6704,6705],{"color":1036,"icon":1037},[435,6699,6700],{},"Interview tip — Auth + RLS Integration:"," Supabase Auth dùng JWT. Khi user login, JWT được lưu trong localStorage. Khi gọi API, JWT tự động được gửi kèm. RLS policies dùng ",[456,6703,1048],{}," để lấy user ID từ JWT — đây là cách Auth và Database tích hợp với nhau. ",[435,6706,6707],{},"Luôn bật RLS trước khi expose dữ liệu.",[507,6709,6710,6713,6714,6716,6717,6719,6720,6722],{"color":1036,"icon":1037},[435,6711,6712],{},"Interview tip — JWT Claims:"," JWT chứa ",[456,6715,556],{}," (user ID), ",[456,6718,757],{}," (authenticated\u002Fanon), và ",[456,6721,582],{},". Bạn có thể thêm custom claims qua Custom Access Token Hook. Khi token hết hạn, refresh token được dùng để lấy access token mới. Service role key bypass RLS — chỉ dùng trong Edge Functions hoặc backend.",[507,6724,6725,6728],{"color":1036,"icon":1037},[435,6726,6727],{},"Interview tip — Magic Link vs Password:"," Magic Link an toàn hơn password vì: (1) không cần user tạo\u002Fnhớ password, (2) link chỉ dùng một lần và hết hạn nhanh, (3) không có risk brute-force. Tuy nhiên, user cần access email mỗi lần login — không tiện bằng password với \"remember me\".",[507,6730,6731,6734,6735,6738,6739,6742,6743,6745],{"color":1036,"icon":1037},[435,6732,6733],{},"Interview tip — Identity Linking:"," Một user có thể có nhiều identity (email, Google, GitHub, ...). Dùng ",[456,6736,6737],{},"linkIdentity()"," để thêm provider, ",[456,6740,6741],{},"unlinkIdentity()"," để gỡ, ",[456,6744,2888],{}," để xem danh sách. Khi login bằng bất kỳ identity nào đã link, user vào cùng một account. Không thể unlink identity cuối cùng.",[507,6747,6748,6751],{"color":1036,"icon":1037},[435,6749,6750],{},"Interview tip — Refresh Flow:"," Access token (JWT) sống 1 giờ, refresh token sống 7 ngày. Client SDK tự động refresh access token khi hết hạn — developer không cần code logic này. Khi cả hai đều hết hạn, user phải login lại. Đây là balance giữa security (token ngắn) và UX (không login thường xuyên).",[507,6753,6754,6757,6758,6761,6762,6764,6765,6768],{"color":1036,"icon":1037},[435,6755,6756],{},"Interview tip — Server-Side Auth:"," Khi dùng Next.js\u002FNuxt\u002FSvelteKit SSR, dùng ",[456,6759,6760],{},"createServerClient"," từ ",[456,6763,3741],{}," để verify user ở server. Service role key cho admin operations — tuyệt đối không expose ra client. Pattern phổ biến: client gửi JWT trong Authorization header -> server dùng ",[456,6766,6767],{},"supabase.auth.admin.getUser(jwt)"," để verify.",[1146,6770],{},[427,6772,6774],{"id":6773},"bài-tập","Bài tập",[6776,6777,6778,6831,6857,6882],"field-group",{},[6779,6780,6782],"field",{"name":6781},"Bài tập 4: Full Auth Flow — Đăng ký, xác minh, đăng nhập, quản lý session",[460,6783,6784,6787,6796,6822,6828],{},[463,6785,6786],{},"Setup Supabase project với email verification bắt buộc",[463,6788,6789,6790,6792,6793,6795],{},"Tạo Nuxt\u002FVue app với trang Sign Up (",[456,6791,1211],{},"), Sign In (",[456,6794,1357],{},"), Dashboard",[463,6797,6798,6799],{},"Implement flow:\n",[1546,6800,6801,6804,6807,6810,6816,6819],{},[463,6802,6803],{},"User đăng ký -> nhận email verification",[463,6805,6806],{},"Verify email -> redirect về app",[463,6808,6809],{},"User đăng nhập -> nhận JWT session",[463,6811,6812,6813],{},"Dashboard hiển thị user info từ ",[456,6814,6815],{},"getUser()",[463,6817,6818],{},"Auto-refresh session khi access token hết hạn",[463,6820,6821],{},"Sign out và redirect về trang login",[463,6823,6824,6825,6827],{},"Theo dõi ",[456,6826,1140],{}," để sync UI với auth state",[463,6829,6830],{},"Middleware bảo vệ route: redirect về login nếu chưa đăng nhập",[6779,6832,6834],{"name":6833},"Bài tập 5: Google OAuth + Magic Link",[460,6835,6836,6839,6842,6845,6851,6854],{},[463,6837,6838],{},"Cấu hình Google OAuth provider trong Supabase Dashboard",[463,6840,6841],{},"Tạo nút \"Sign in with Google\" trong app",[463,6843,6844],{},"Implement Magic Link như alternative login method",[463,6846,6847,6848,6850],{},"Sau khi OAuth login, tạo record trong bảng ",[456,6849,518],{}," (dùng Database Trigger)",[463,6852,6853],{},"Xử lý case: user dùng Google OAuth và Magic Link với cùng một email",[463,6855,6856],{},"Bonus: Thêm GitHub OAuth provider",[6779,6858,6860],{"name":6859},"Bài tập 6: MFA với TOTP",[460,6861,6862,6865,6868,6871,6876,6879],{},[463,6863,6864],{},"Enable MFA trong Supabase project",[463,6866,6867],{},"Tạo flow enroll MFA: user scan QR code với Google Authenticator",[463,6869,6870],{},"Tạo flow challenge: sau khi login password, yêu cầu TOTP code",[463,6872,6873,6874,1673],{},"Verify TOTP code và lấy ",[456,6875,986],{},[463,6877,6878],{},"Xử lý recovery codes khi user mất Authenticator app",[463,6880,6881],{},"Bonus: Gửi SMS OTP như MFA factor thứ hai",[6779,6883,6885],{"name":6884},"Bài tập 7: Custom Access Token Hook",[460,6886,6887,6900,6910,6913,6916],{},[463,6888,6889,6890,519,6893,4824,6896,6899],{},"Tạo bảng ",[456,6891,6892],{},"public.roles",[456,6894,6895],{},"user_id",[456,6897,6898],{},"role_name"," (admin, editor, viewer)",[463,6901,6902,6903,6906,6907,6909],{},"Viết ",[456,6904,6905],{},"custom_access_token_hook"," Postgres function để thêm ",[456,6908,757],{}," vào JWT claims",[463,6911,6912],{},"Enable hook trong Dashboard",[463,6914,6915],{},"Viết RLS policy dùng claim từ JWT: admin được edit tất cả posts, editor được edit posts của họ, viewer chỉ được read",[463,6917,6918],{},"Test: login với user có role khác nhau, kiểm tra quyền truy cập",[1146,6920],{},[6922,6923],"navigation",{":next":6924,":prev":6924,"nextIcon":130,"nextTitle":265,"nextTo":6925,"prevIcon":68,"prevTitle":256,"prevTo":6926},"true",".\u002F3.storage-realtime",".\u002F1.database",[6928,6929,6930],"style",{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}",{"title":458,"searchDepth":549,"depth":549,"links":6932},[6933,6939,6949,6952,6958,6961,6962,6963,6964,6965,6971,6972],{"id":429,"depth":549,"text":11,"children":6934},[6935,6936,6937,6938],{"id":446,"depth":577,"text":447},{"id":526,"depth":577,"text":527},{"id":1061,"depth":577,"text":1062},{"id":1122,"depth":577,"text":1123},{"id":1150,"depth":549,"text":1151,"children":6940},[6941,6942,6943,6944,6945,6946,6947,6948],{"id":1154,"depth":577,"text":1155},{"id":1421,"depth":577,"text":1422},{"id":1559,"depth":577,"text":1560},{"id":1828,"depth":577,"text":1829},{"id":2336,"depth":577,"text":2337},{"id":2435,"depth":577,"text":2436},{"id":2518,"depth":577,"text":2519},{"id":2892,"depth":577,"text":2893},{"id":2958,"depth":549,"text":2959,"children":6950},[6951],{"id":3141,"depth":577,"text":3142},{"id":3701,"depth":549,"text":3702,"children":6953},[6954,6955,6956,6957],{"id":3708,"depth":577,"text":3709},{"id":4177,"depth":577,"text":4178},{"id":4329,"depth":577,"text":4330},{"id":4593,"depth":577,"text":4594},{"id":4816,"depth":549,"text":4817,"children":6959},[6960],{"id":5211,"depth":577,"text":5212},{"id":5251,"depth":549,"text":5252},{"id":5619,"depth":549,"text":5620},{"id":5833,"depth":549,"text":5834},{"id":6234,"depth":549,"text":6235},{"id":6305,"depth":549,"text":6306,"children":6966},[6967,6968,6969,6970],{"id":6309,"depth":577,"text":6310},{"id":6420,"depth":577,"text":6421},{"id":6455,"depth":577,"text":6456},{"id":6604,"depth":577,"text":6605},{"id":6694,"depth":549,"text":6695},{"id":6773,"depth":549,"text":6774},"Supabase Auth — email\u002Fpassword, magic link, OTP, 50+ OAuth providers, SSO, MFA, passkey, auth hooks.","md",null,{},{"title":260,"icon":263},{"title":422,"description":6973},"iPlqs2P9n8FitKmZwE1mPOSkRNHijMT8QCYgq8WLqpY",[6981,6983],{"title":256,"path":257,"stem":258,"description":6982,"icon":68,"children":-1},"Supabase Database — PostgreSQL với Row Level Security, Supavisor connection pooling, 20+ extensions, backup PITR.",{"title":265,"path":266,"stem":267,"description":6984,"icon":268,"children":-1},"Supabase Storage (S3-compatible, CDN, image transform) và Realtime (Broadcast, Presence, Postgres Changes CDC).",1783490367966]