มาทำ Token-based Authentication แบบง่าย ๆ กันเถอะ (ตอนที่ 2)

คราวที่แล้วเราลองทำระบบ Authen โดยใช้ Token กันไปแล้วนะครับ แต่ระบบของเราก็ยังไม่สมบูรณ์ซะทีเดียว

ตอนนี้ Access Token ของเรามีอายุ 5 นาที แต่ Refresh Token ของเรายังไม่มีวันหมดอายุนะครับ

ในบทความนี้เราจะเพิ่ม feature เข้าไป 2 อย่างครับ

  1. Revoke Refresh Token — ใช้เพื่อ Sign out
  2. กำหนดเวลาหมดอายุให้ Refresh Token

Revoke Refresh Token

เนื่องจาก Refresh Token ของเราเป็น Stateful นะครับ เราสามารถลบออกจาก Database ได้เลย พอลบแล้ว User ก็จะไม่สามารถใช้ Refresh Token อันเดิมเพื่อขอ Access Token อันใหม่ได้

อย่างแรกก็ไปเพิ่ม Handler ใน Auth Service นะครับ

  • service/auth.go
// Auth service
func Auth(g *echo.Group) {
...
g.Post("/revoke", authRevokeHandler, verifyAccessTokenMiddleware)
}

สังเกตว่า ผมจะให้ส่ง Access Token มาด้วยนะครับ เพราะใส่ verifyAccessTokenMiddleware มาด้วย แสดงว่าเราก็ต้องส่ง Access Token มาใน Header ด้วย

แล้วก็ไปเพิ่ม function handler นะครับ

type authRevokeRequest struct {
Token string `json:"token"`
}
func authRevokeHandler(c echo.Context) error {
var body authRevokeRequest
var err error
if err = c.Bind(&body); err != nil || body.Token == "" {
return c.String(http.StatusBadRequest, "Bad Request")
}
if err = api.DeleteToken(body.Token); err != nil {
log.Println(err)
return c.String(http.StatusInternalServerError, "Internal Server Error")
}
return c.String(http.StatusOK, "OK")
}

เสร็จแล้วก็ลองยิง request ดูครับ

Revoke Refresh Token

อย่าลืมใส่ Header มาด้วยนะครับ

Authorization: Bearer ACCESS_TOKEN
Revoke Refresh Token Header

หลังจากนั้นก็ลองส่ง request ไปขอ Access Token โดยใช้ Refresh Token ที่พึ่งลบไปดูครับ

Request Access Token from revoked refresh token

กำหนดเวลาหมดอายุให้ Refresh Token

ตอนนี้ Refresh Token สามารถลบออกจาก Database ได้แล้วนะครับ แต่ว่ายังไม่มีเวลาหมดอายุ เช่นเราอยากให้ Refresh Token ที่ Last Access มาล่าสุดนานกว่า 7 วัน ให้ถือว่าหมดอายุ เราลองมาเขียน Code กันดูครับ

  • api/token.go
// ValidateToken validate and update token last access timestamp
func ValidateToken(token string, userID int64, expiresInFromLastAccess time.Duration) (bool, error) {
tk, err := getToken(token)
if err != nil {
return false, err
}
if tk == nil || tk.UserID != userID {
return false, nil
}
if time.Now().After(tk.LastAccessAt.Add(expiresInFromLastAccess)) {
// token expired
// remove expired token from database
go DeleteToken(token)
return false, nil
}
tk.Stamp()
go func(tk model.Token) {
ctx, cancel := getContext()
defer cancel()
client.Put(ctx, tk.Key(), &tk)
}(*tk)
return true, nil
}

เราแก้ code ในส่วนของ ValidateToken นะครับ โดนดูว่าถ้า Token หมดอายุแล้วให้ลบออกจาก Database ด้วย

หรืออยากจะเก็บเวลาหมดอายุไว้ใน Database ก็ได้ครับ แล้วเวลาที่ ValidateToken ก็ให้ต่ออายุ Token ด้วย เหมือนกับเปลี่ยนเวลา Last Access ครับ

  • service/auth.go
const refreshTokenDuration = time.Duration(time.Hour * 24 * 7)
func authTokenHadler(c echo.Context) error {
...
if body.GrantType == grantTypeRefreshToken {
...
if ok, err := api.ValidateToken(body.RefreshToken, claims.ID, refreshTokenDuration); !ok {
...
}

อย่าลืมไปเปลี่ยนใน authTokenHandler ด้วยนะครับ ให้ใส่เวลาหมดอายุของ Refresh Token เข้าไปด้วย


ตอนนี้ระบบ Authen ของเราก็สามารถ Sign out และ Refresh Token มีวันหมดอายุแล้วนะครับ

แต่ระบบนี้ก็ยังไม่สมบูรณ์นะครับ เพราะยังไม่สามารถลบ Refresh Token ที่ไม่ได้ใช้แล้วได้

ไว้คราวหน้า เรามาทำระบบนี้กันต่อนะครับ xD

Like what you read? Give acoshift a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.