wมาลองเล่น OAuth 2.0 กับ Auth0 กัน (Part 1)

Nattawat Songsom
6 min readJun 7, 2023

--

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 ตามภาพกัน

  1. user กด login link บน web client ของเรา
  2. web app ทำการ redirect user ไปยัง web ของ auth0 ใน path /authorize
  3. แสดงหน้า login ของ web auth0 แก่ user (auth0 จะทำการจัดการหน้าเว็บที่ใช้ login ให้ และจัดการ database ข้อมูล user ให้)
  4. user ทำการ login และให้ consent ในการแชร์ข้อมูลแก่ web client ของเรา
  5. เมื่อ login และให้ consent สำเร็จ เว็บ login ของ auth0 จะ redirect user กลับมาที่เว็บของเรา พร้อมกับส่ง authroization code มาให้
  6. web client ของเรานำ authorization code ที่ได้ พร้อมกับ client id และ client secret มายิง api ของ auth0 (path api คือ /oauth/token)
  7. จากการยิง api ในข้อ 6, server auth0 จะนำ authorization code, client id, client secret ที่ได้ มาเช็คว่า valid มั้ย
  8. ถ้า valid server auth0 จะส่ง id token, access token (และอาจจะมี refresh token ด้วย) ไปให้กับ web client ของเรา
  9. web client ของเราสามารถนำ access token ที่ได้ มาขอข้อมูลของ user จาก server auth0 ได้
  10. server auth0 ให้ข้อมูล user แก่ web client จากนั้น web client สามารถนำข้อมูลนี้ไปสร้างบัญชีในระบบตัวเองได้

โอเค flow เป็นประมาณนี้

มาลองทำจริงกัน โดยเราจะลองทำตามตัวอย่างของ auth0 สำหรับ nextjs กันก่อน

Tutorial

  1. clone repo https://github.com/auth0-samples/auth0-nextjs-samples/tree/main
  2. เข้าไป folder Sample-01 แล้วรันโปรเจ็ก
  3. 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 นี้น่าจะประมาณนี้ครับ

Referrence

--

--

No responses yet