tutorial

122 Studi Kasus: Sistem Registrasi & Login JWT dengan gqlgen

122 Studi Kasus: Sistem Registrasi & Login JWT dengan gqlgen

Membangun sistem autentikasi yang aman dan efisien adalah salah satu pondasi hampir seluruh aplikasi backend modern. Di era API-first development dan microservices, JSON Web Token (JWT) menjadi salah satu solusi authentication yang paling populer. Di kasus kali ini, saya akan membagikan studi kasus membangun sistem registrasi & login menggunakan JWT dengan GraphQL di Go, memanfaatkan library powerful gqlgen.

Kita akan membahas arsitektur, contoh kode, hingga flow diagram alur request – lengkap dengan simulasi login dan proteksi endpoint.


Gambaran Umum Sistem

Sistem yang akan kita buat mencakup fitur berikut:

  1. Registrasi User
  2. Login User
  3. Proteksi Endpoint dengan JWT

Komponen utama yang digunakan:

  • Go sebagai backend language
  • gqlgen sebagai GraphQL code generator
  • gorilla/mux (opsional) sebagai HTTP router
  • JWT (github.com/golang-jwt/jwt/v5) untuk pembuatan dan validasi token
  • bcrypt untuk hashing password

Database dan Struktur User

Untuk simplicity, kita gunakan in-memory storage berupa map (map[string]*User). Di dunia nyata, Anda bisa dengan mudah menggantinya dengan Postgres atau MongoDB.

type User struct {
    ID       string
    Username string
    Password string // Hashed!
}

Secara sederhana, struktur storage-nya:

FieldTipeKeterangan
IDstringUUID
UsernamestringUnique, digunakan login
PasswordstringSudah di-hash dengan bcrypt

Skema GraphQL

Untuk kebutuhan registrasi dan login, cukup dua mutation sederhana:

type User {
    id: ID!
    username: String!
}

type AuthPayload {
    token: String!
    user: User!
}

type Mutation {
    register(username: String!, password: String!): AuthPayload!
    login(username: String!, password: String!): AuthPayload!
}

type Query {
    me: User
}

Proses Registrasi

Langkah-langkah:

  1. Input username & password user.
  2. Cek apakah username sudah dipakai.
  3. Hash password: bcrypt.
  4. Simpan user baru ke database.
  5. Generate JWT token.
  6. Kembalikan token & data user.
func (r *mutationResolver) Register(ctx context.Context, username string, password string) (*model.AuthPayload, error) {
    if _, exists := users[username]; exists {
        return nil, errors.New("username already exists")
    }
    hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
    if err != nil {
        return nil, err
    }
    user := &User{
        ID:       uuid.NewString(),
        Username: username,
        Password: string(hashed),
    }
    users[username] = user
    token, err := generateJWT(user)
    if err != nil {
        return nil, err
    }
    return &model.AuthPayload{
        Token: token,
        User:  &model.User{ID: user.ID, Username: user.Username},
    }, nil
}

Proses Login

Simulasinya sangat mirip, hanya tahap validasi password diganti menjadi compare hash.

func (r *mutationResolver) Login(ctx context.Context, username string, password string) (*model.AuthPayload, error) {
    user, exists := users[username]
    if !exists {
        return nil, errors.New("invalid credentials")
    }
    if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
        return nil, errors.New("invalid credentials")
    }
    token, err := generateJWT(user)
    if err != nil {
        return nil, err
    }
    return &model.AuthPayload{
        Token: token,
        User:  &model.User{ID: user.ID, Username: user.Username},
    }, nil
}

Pembuatan & Validasi JWT

JWT dibuat saat register/login, dan di-validasi setiap kali akses endpoint yang protected.

Func generateJWT():

func generateJWT(user *User) (string, error) {
    claims := jwt.MapClaims{
        "sub": user.ID,
        "username": user.Username,
        "exp": time.Now().Add(time.Hour * 24).Unix(),
    }
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString([]byte(SECRET_KEY))
}

Func parseJWT():

func parseJWT(tokenStr string) (*User, error) {
    token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
        return []byte(SECRET_KEY), nil
    })
    if err != nil || !token.Valid {
        return nil, errors.New("invalid token")
    }
    claims, ok := token.Claims.(jwt.MapClaims)
    if !ok {
        return nil, errors.New("invalid token claims")
    }
    username := claims["username"].(string)
    user, exists := users[username]
    if !exists {
        return nil, errors.New("user not found")
    }
    return user, nil
}

Middleware Proteksi Endpoint

Di gqlgen, kita inject user ke context, lalu di resolver tertentu, kita bisa cek authentication.

func AuthMiddleware() func(ctx context.Context) context.Context {
    return func(ctx context.Context) context.Context {
        token := extractBearerToken(ctx)
        if token == "" {
            return ctx
        }
        user, err := parseJWT(token)
        if err != nil {
            return ctx
        }
        return context.WithValue(ctx, "user", user)
    }
}

Kemudian, misalnya, di query me:

func (r *queryResolver) Me(ctx context.Context) (*model.User, error) {
    user := ctx.Value("user")
    if user == nil {
        return nil, errors.New("unauthenticated")
    }
    u := user.(*User)
    return &model.User{ID: u.ID, Username: u.Username}, nil
}

Diagram Alur

Mari ilustrasikan proses alur registrasi dan login menggunakan Mermaid:

sequenceDiagram
    participant Client
    participant Server

    Client->>Server: register(username, password)
    Server->>Server: cek username unik?
    alt username sudah ada
        Server-->>Client: error
    else username OK
        Server->>Server: hash password
        Server->>Server: simpan user ke DB
        Server->>Server: generate JWT
        Server-->>Client: {token, user}
    end
    
    Client->>Server: login(username, password)
    Server->>Server: ambil data user dari DB
    alt user tidak ditemukan
        Server-->>Client: error
    else ada user
        Server->>Server: verifikasi hash password
        alt invalid credentials
            Server-->>Client: error
        else sukses
            Server->>Server: generate JWT
            Server-->>Client: {token, user}
        end
    end

Simulasi Client

Misal mutation GraphQL yang di-post:

1. Registrasi

mutation {
  register(username: "andi", password: "rahasia") {
    token
    user { id username }
  }
}

Respon:

{
  "data": {
    "register": {
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI... (JWT)",
      "user": { "id": "c780...", "username": "andi" }
    }
  }
}

2. Login

mutation {
  login(username: "andi", password: "rahasia") {
    token
    user { id username }
  }
}

3. Query Data Diri (Proteksi)

Sertakan header: Authorization: Bearer {token}

query {
  me { id username }
}

Tabel Perbandingan: REST vs GraphQL + JWT

FiturREST + JWTGraphQL + JWT
Endpoint Registrasi/Login/register, /login1 endpoint (/graphql), mutation berbeda
Proteksi AuthMiddleware per routeMiddleware per field/resolver
ExtensibilitasNeeds new endpointTambahkan saja di schema
TransportJSONGraphQL Query

Catatan Keamanan

  • Jangan pernah menyimpan password tanpa hash.
  • JWT sebaiknya beri expiry (exp) yang wajar.
  • Gunakan HTTPS!
  • Validasi input user (XSS, SQLI, dll.)

Penutup

Dengan pattern seperti di atas, Anda bisa deploy aplikasi GraphQL + JWT authentication dengan cepat dan aman, memanfaatkan keunggulan type safety Go plus performa gqlgen. Di dunia nyata, Anda tinggal menambah koneksi ke Postgres atau MongoDB serta menerapkan security yang lebih ketat seperti rate limiting dan monitoring.

Semoga studi kasus sederhana registrasi & login JWT dengan gqlgen ini bisa menjadi fondasi implementasi authentication modern di project Anda berikutnya.

Happy hacking! 🚀


Kode Demo Lengkap:
github.com/youruser/gqlgen-jwt-example (dummy repository, silakan ganti dengan repo sendiri)

comments powered by Disqus