wมาลองเล่น OAuth 2.0 กับ Auth0 กัน (Part 1)
simple authorization code flow
โดย OAuth 2.0 ที่ auth0 มี tutorial สอน มี 4 flow ได้แก่
- Authorization Code Flow
- Implicit Flow
- Resource Owner Password Flow
- Client Credentials Flow
โอเค วันนี้เราจะมาลองทำ Authorization Code Flow กัน
Authorization Code Flow
มาดู flow จากภาพกันก่อน
สมมุติว่า เราจะสร้างเว็บ todo list
โดยเราไม่ต้องการให้ user มาสร้างบัญชี บนเว็บ todo list ของเราตรงๆ
แต่เราอยากให้ user สามารถนำบัญชีที่มีอยู่แล้วในระบบกลางมาใช้ได้
(คล้ายๆกับ medium ที่สามารถ login ด้วย google account ได้ โดยไม่ต้องไปกรอกข้อมูลเพื่อสร้างบัญชีใหม่ทั้งหมดตั้งแต่ต้น แต่ดึงข้อมูลจาก google account มาสร้างบัญชี medium ได้)
จากระบบตัวอย่างนี้ มาไล่ flow ทีละ step ตามภาพกัน
- user กด login link บน web client ของเรา
- web app ทำการ redirect user ไปยัง web ของ auth0 ใน path /authorize
- แสดงหน้า login ของ web auth0 แก่ user (auth0 จะทำการจัดการหน้าเว็บที่ใช้ login ให้ และจัดการ database ข้อมูล user ให้)
- user ทำการ login และให้ consent ในการแชร์ข้อมูลแก่ web client ของเรา
- เมื่อ login และให้ consent สำเร็จ เว็บ login ของ auth0 จะ redirect user กลับมาที่เว็บของเรา พร้อมกับส่ง authroization code มาให้
- web client ของเรานำ authorization code ที่ได้ พร้อมกับ client id และ client secret มายิง api ของ auth0 (path api คือ /oauth/token)
- จากการยิง api ในข้อ 6, server auth0 จะนำ authorization code, client id, client secret ที่ได้ มาเช็คว่า valid มั้ย
- ถ้า valid server auth0 จะส่ง id token, access token (และอาจจะมี refresh token ด้วย) ไปให้กับ web client ของเรา
- web client ของเราสามารถนำ access token ที่ได้ มาขอข้อมูลของ user จาก server auth0 ได้
- server auth0 ให้ข้อมูล user แก่ web client จากนั้น web client สามารถนำข้อมูลนี้ไปสร้างบัญชีในระบบตัวเองได้
โอเค flow เป็นประมาณนี้
มาลองทำจริงกัน โดยเราจะลองทำตามตัวอย่างของ auth0 สำหรับ nextjs กันก่อน
Tutorial
- clone repo https://github.com/auth0-samples/auth0-nextjs-samples/tree/main
- เข้าไป folder Sample-01 แล้วรันโปรเจ็ก
- create app ใหม่ใน auth0 dashboard
4. เลือก application เป็นแบบ regular web app
5. เลือก tech เป็น nextjs
6. setup allowed callback url เป็น http://localhost:3000/api/auth/callback ใน settings
7. setup allowed logout url เป็น http://localhost:3000
8. โอเค จัดการ auth0 dashboard เสร็จแล้ว กลับมาที่ repo ที่ clone มากัน
จริงๆ ถ้าเป็น repo nextjs เปล่าเราต้องลง lib ด้วยคำสั่ง
npm install @auth0/nextjs-auth0
แต่ใน repo ที่เรา clone มา ลง lib นี้ไว้แล้ว โอเค ไปต่อกัน
9. จัดการ env ไฟล์ที่ต้องใช้ โดยประกอบด้วย
AUTH0_SECRET = secret ที่ใช้ในการเข้ารหัส session cookie อันนี้ใช้ค่าจากการ generate ได้ด้วยคำสั่ง openssl rand -hex 32
AUTH0_BASE_URL= domain base ของ web โดยปกติแล้ว nextjs จะมี base url เป็น http://localhost:3000
AUTH0_ISSUER_BASE_URL=โดเมนของหลังบ้าน auth0 ของโปรเจ็กต์ โดยดูได้จาก auth0 dashboard setting โดยใช้ format https
AUTH0_CLIENT_ID=ไอดีของโปรเจ็กต์ auth0 ดูได้จาก dashboard setting
AUTH0_CLIENT_SECRET=secret ของ โปรเจ็กต์ auth0 ดูได้จาก dashboard setting
โอเค โดย env เหล่านี้เราจะใส่ในไฟล์ .env.local
10. ใน nextjs ทำการสร้าง api สำหรับ handle การ auth
โดยเราจะสร้าง 4 api ได้แก่
/api/auth/login
: ใช้ในการ login โดยจะเช็คข้อมูล user กับทาง auth0/api/auth/logout
: ใช้ในการ logout ออกจากเว็บของเรา/api/auth/callback
: ใช้ในการ redirect user หลังจาก login กับทาง auth0 เสร็จ/api/auth/me
: ใช้ในการ fetch profile user จาก auth0
ในการสร้าง api เหล่านี้ใน nextjs เราจะสร้าง dynamic path สำหรับ route/auth/*
ซึ่งทำได้โดยการสร้างไฟล์ pages/api/auth/[...auth0].js
และมีเนื้อหาไฟล์ดังนี้
// pages/api/auth/[...auth0].js
import { handleAuth } from '@auth0/nextjs-auth0';
export default handleAuth();
โอเค เราสร้าง api (ใน nextjs) สำหรับเชื่อมต่อกับหลังบ้าน auth0 แล้ว มาเขียนฝั่ง client nextjs ของเราต่อกัน
เร่ิมจากการใส่ provider ของ auth0 เพื่อให้ client app ของเรา รู้จักวิธีในการดึงค่าต่างๆของ user
โดยเราจะใส่ provider ในไฟล์ _app.jsx ดังนี้
import React from 'react';
import { UserProvider } from '@auth0/nextjs-auth0/client';
import Layout from '../components/Layout';
import '@fortawesome/fontawesome-svg-core/styles.css';
import initFontAwesome from '../utils/initFontAwesome';
import '../styles/globals.css';
initFontAwesome();
export default function App({ Component, pageProps }) {
return (
<UserProvider>
<Layout>
<Component {...pageProps} />
</Layout>
</UserProvider>
);
}
จากนั้นสร้าง link สำหรับเรียก api /api/auth/login ที่เราสร้างไว้
โดยจาก repo ตัวอย่าง จะทำการใส่ link สำหรับเรียก api ไว้ที่ navbar
โดยจะทำการเช็คก่อนว่ามีค่า user มั้ย (login แล้วยัง) และ เช็คว่ากำลัง load ค่ามาเช็คอยู่มั้ย (isLoading) โดยถ้าไม่ได้ login ที จะแสดง link login นั่นเอง
ซึ่งจาก flow ที่เราอธิบายไปก่อนหน้านี้ ส่วนนี้จะนับเป็นข้อ 1 ใน flow นั่นเอง
โดย เมื่อกดปุ่ม login เพื่อเรียก api /api/auth/login จะทำการ redirect user ไปที่เว็บ login ของ auth0
โดย default เว็บ login ที่ redirect มา จะเป็นเว็บที่ auth0 generate ให้ และข้อมูลของ user ที่ใช้ในการสมัคร / login นี้ จะเก็บไว้ที่หลังบ้านของ auth0 เอง
เราจะเรียกการ login แบบนี้ว่า universal login
แต่เราก็สามารถ custom หน้าเว็บ login และ custom ข้อมูล user ที่สามารถ login ได้เช่นกัน โดยจะอยู่ในหัวข้อ provision users ใน doc auth0 แต่เราจะยังไม่ไปหัวข้อนั้นกันก่อน
โอเค กลับมาที่การเรียก api /api/auth/login
เมื่อเราเรียก api นี้ web client ของเราจะ redirect ไปที่เว็บ authorize ของ auth0 (โดยระบุ scope ข้อมูล user ที่เราอยากได้ไปด้วย) ตามภาพ
ซึ่งจาก flow ที่เราอธิบายไปตอนแรก ขั้นนี้จะอยู่ในข้อ 2 นั่นเอง
จากนั้น เว็บ authorize ของ auth0 จะทำการ redirect user ไปที่เว็บ /login ตามภาพ
ซึ่งในขั้นนี้จะเป็น flow ข้อ 3 นั่นเอง
โดยเมื่อเรา login / signup บนหน้านี้เสร็จ เว็บ auth0 จะทำการเช็คว่า เคยให้ข้อมูลของ user ที่เราขอใน scope แก่ web client ของเราแล้วยัง
ถ้ายังไม่เคยให้ เว็บ auth0 จะ redirect ไปที่หน้า consent เพื่อทำการขอสิทธ์เราในการแชร์ข้อมูลให้กับ client nextjs ที่เราสร้าง
ซึ่งการ login / ให้ consent นี้ จะอยู่ใน flow ข้อที่ 4 นั่นเอง
ต่อมา web auth0 จะทำการ redirect ไปที่ api /api/auth/callback โดยจะให้ code มาด้วยตามรูป
ซึ่งในขั้นนี้ จะเป็นไปตาม flow ข้อที่ 5 นั่นเอง
นอกจาก code ที่ให้มากับการ redirect /callback ยังมีการ set session มาที่ browser ของ client nextjs อีกด้วย
โดย session cookie นี้จะเป็นตัวบ่งบอกว่าเรา login อยู่ และสามารถนำไป get profile ได้
โดยในการดึง profile ของ user สามารถใช้ hook useUser ดึงมาได้เลย ตาม code
import React from 'react';
import { useUser } from '@auth0/nextjs-auth0/client';
export default function Profile() {
const { user, error, isLoading } = useUser();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>{error.message}</div>;
return (
user && (
<div>
<img src={user.picture} alt={user.name} />
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
)
);
}
โดยข้อมูล user จะได้มาจาก api /api/auth/me ซึ่งทาง browser client ได้ส่ง session cookie ของ user ไปเพื่อเอาข้อมูลมา
จะสังเกตเห็นว่าการ flow ในการดึงข้อมูล user ไม่ได้เป็นไปภาพ flow ข้อที่ 6–10 แบบเป๊ะๆ
จากภาพจะเห็นว่าเราหาการยิง api /oauth/token ไม่เจอ
ทั้งๆที่ในข้อ 6 บอกว่าตัว auth0 sdk ที่เราใช้ มีการนำ authorization code ไปยิง api /oauth/token ไปใช้
น่าจะเป็นเพราะว่าตัว web client ของเรามีการเก็บ cookie session ไว้ที่ฝั่ง server side ของตัว web client เอง จากนั้นฝั่ง server side ก็ไปทำการยิง api ข้อ 6–10 กับ server auth0 ทำให้เราไม่สามารถ inspect การยิง api เหล่านี้ผ่าน server ได้
เช่นในรูป เมื่อเรายิง api ฝั่ง server side ของเราเองเพื่อเอาข้อมูล user, จะมีการนำ cookie session ไปใช้ และได้ข้อมูล user กลับมา
แล้วถ้าเราอยากเอา authorization code ไปแลก access token เอง (อยากทำตาม flow ข้อ 6–10 เอง) ละ?
พอไปค้น doc auth0 ดูก็พบว่า มี doc ให้ ตาม link
ซึ่งเราจะมาลองทำกันต่อใน part ต่อไปนั่นเอง
โอเค part นี้น่าจะประมาณนี้ครับ