tutorial

116 Menggunakan Context untuk Autentikasi di gqlgen

116 Menggunakan Context untuk Autentikasi di gqlgen

Mengelola autentikasi adalah bagian penting dalam membangun API modern, termasuk GraphQL. Di ekosistem Go, gqlgen telah menjadi salah satu library yang sangat populer untuk membangun GraphQL API, karena fleksibilitas dan kemampuannya yang sesuai dengan gaya native Go. Salah satu fitur powerful yang sering digunakan untuk authentikasi di gqlgen adalah penggunaan context.Context. Pada artikel ini, saya akan membedah bagaimana menggunakan Context untuk autentikasi di gqlgen, dilengkapi dengan contoh kode, simulasi alur, beserta best practice yang telah terbukti di lapangan.


Mengapa Context di Go itu Istimewa?

Sebelum masuk ke implementasi, mari kita pahami mengapa context.Context sering digunakan sebagai “kendaraan” utama untuk menyimpan dan membawa data autentikasi pada setiap permintaan.

  • Thread-safe: Data pada Context hanya berlaku per-request (bukan global mutable state).
  • Hierarki/scoping: Setiap request memiliki context sendiri, yang bisa di-extend (turunan).
  • Cancelation-aware: Context bisa digunakan untuk membatalkan permintaan (timeout/token).
  • Transparansi: Data bisa diambil dari mana saja selama punya referensi context.

Inilah mengapa, pada GraphQL API di Go—termasuk ketika menggunakan gqlgen—Context adalah tempat terbaik untuk menyimpan data user yang sudah diverifikasi, tanpa harus mengorbankan keamanan dan concurrency.


Bagaimana Alur Autentikasi di gqlgen?

Secara umum, proses autentikasi di gqlgen dengan Context berjalan sebagaimana berikut:

  1. Client mengirim query/mutasi beserta token (biasanya JWT) di header HTTP
  2. Server middleware membaca Authorization Header
  3. Middleware memverifikasi token dan menaruh data user ke context
  4. Resolver mengambil user dari context dan melanjutkan processing

Mari kita ilustrasikan alur ini dengan diagram Mermaid:

sequenceDiagram
    participant Client
    participant Server(Middleware)
    participant Application(Resolver)
    Client->>Server: Kirim Query + Header Authorization
    Server->>Server: Validasi & parsing JWT
    alt Token Valid
        Server->>Server: Taruh user ke Context
        Server->>Application: Panggil Resolver (dengan context)
        Application->>Application: Ambil user dari Context
        Application-->>Client: Kirim hasil
    else Token Tidak valid
        Server-->>Client: Kirim error Unauthorized
    end

Step-by-Step Implementasi

Langkah implementasi bisa kita bagi menjadi tiga bagian utama:

  1. Membuat Middleware untuk Autentikasi
  2. Menyimpan & Mengambil User di Context
  3. Mengakses User di Resolver

Mari kita bedah satu per satu.

1. Membuat Middleware Autentikasi (HTTP Layer)

Pada umumnya, GraphQL endpoint di-serve lewat HTTP handler seperti mux, gin, atau bahkan net/http standar. Di sini kita pasang middleware yang akan melakukan check Authorization header.

Contoh implementasi middleware sederhana dengan net/http:

func AuthMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        tokenString := r.Header.Get("Authorization")
        if tokenString == "" {
            http.Error(w, "missing token", http.StatusUnauthorized)
            return
        }

        // Misal: verifikasi JWT dan extraction user ID/email
        user, err := verifyAndExtractUser(tokenString)
        if err != nil {
            http.Error(w, "invalid token", http.StatusUnauthorized)
            return
        }

        // Simpan ke context
        ctx := context.WithValue(r.Context(), "currentUser", user)
        r = r.WithContext(ctx)

        next.ServeHTTP(w, r)
    })
}

Disini, kita membaca JWT dari header, memverifikasinya dengan fungsi (misal) verifyAndExtractUser, dan menyimpan objek user ke context request. Selanjutnya, HTTP handler yang membungkus gqlgen akan menerima context yang sudah ada currentUser.

Tabel simulasi bagaimana Context terbentuk:

StepContext State
Request masukKosong
Auth header dibacaKosong
JWT diverifikasi-
User ditemukanAda currentUser
Handler diteruskanAda currentUser

2. Menyimpan dan Mengakses User di Context gqlgen

Di gqlgen, setiap resolver akan menerima argument pertama yaitu context.

Definisikan terlebih dahulu tipe user yang kita simpan di context:

// models/user.go
type User struct {
    ID    string
    Email string
    Role  string
}

Untuk mengambil data user dari context, kita buat helper function:

// auth/context.go
type contextKey string

const userCtxKey = contextKey("currentUser")

func ForContext(ctx context.Context) *User {
    raw, ok := ctx.Value(userCtxKey).(*User)
    if !ok {
        return nil
    }
    return raw
}

Pada middleware, kita simpan dengan key yang sama:

ctx := context.WithValue(r.Context(), userCtxKey, user)

3. Mengakses User di Resolver gqlgen

Pada waktu implementasi resolver, kita cukup ambil user dari context:

func (r *queryResolver) Me(ctx context.Context) (*model.User, error) {
    user := auth.ForContext(ctx)
    if user == nil {
        return nil, errors.New("unauthorized")
    }
    return user, nil
}

Dengan demikian, setiap resolver apapun bisa mengambil informasi user dari Context tanpa harus tahu mekanisme autentikasi di bawahnya. Separation of concerns terjaga sangat baik.


Penambahan: Role-based Authorization

Setelah user didapat di Context, kita bisa extend skenario menjadi authorization berbasis role, masih menggunakan Context sebagai sumber user.

func (r *mutationResolver) DeleteUser(ctx context.Context, userId string) (bool, error) {
    user := auth.ForContext(ctx)
    if user == nil || user.Role != "admin" {
        return false, errors.New("forbidden")
    }
    // lanjutkan proses
    ...
}

Tabel Simulasi Respon API

Mari kita lihat beberapa skenario simulasi:

TokenMiddlewareResolverOutput
Kosong / tidak adaUnauthorized-401 Unauthorized
Invalid formatUnauthorized-401 Unauthorized
Valid JWT, user tidak ditemukan DBUnauthorized-401 Unauthorized
Valid JWT, user normalOKuser != nilData user
Valid JWT, user role != adminOKForbidden403 Forbidden
Valid JWT, user role = adminOKLanjut/OKSuccess

Best Practices & Tips

  • Simpan data minimal di Context (cukup ID/email/role), untuk efisiensi dan keamanan.
  • Gunakan type-safe key untuk context, hindari collision dengan key string bebas.
  • Pisahkan concerns: Middleware untuk autentikasi, resolver untuk authorization/business logic.
  • Log aktivitas login secara terpusat, jika perlu trace.
  • Kombinasi dengan dataloaders jika ingin efisien fetch data user.

Penutup

Menggunakan Context untuk autentikasi di gqlgen adalah metode yang idiomatic, aman, dan scalable di ekosistem Go. Pattern ini sudah sangat battle-tested, dan scalable dari single instance, microservices, hingga monolith playground.

Semoga dengan contoh kode, simulasi, dan penjelasan di atas, kamu bisa mengimplementasikan GraphQL API Go dengan autentikasi kuat dan clean separation of concerns. Jika ada pertanyaan atau pengalaman menarik, jangan ragu untuk share di komentar!

Happy hacking 🚀

comments powered by Disqus