Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
30 Oct 2025 · 6 min read ·Article 122 / 125
Go

122 Case Study: JWT Registration & Login System with gqlgen

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

122 Case Study: JWT Registration & Login System with gqlgen

Building a secure and efficient authentication system is one of the foundations of almost every modern backend application. In the era of API-first development and microservices, JSON Web Token (JWT) has become one of the most popular authentication solutions. In this case study, I’ll walk through building a registration & login system using JWT with GraphQL in Go, leveraging the powerful gqlgen library.

We’ll cover the architecture, code examples, and a flow diagram of the request flow, complete with a login simulation and endpoint protection.


System Overview

The system we’re going to build includes the following features:

  1. User Registration
  2. User Login
  3. Endpoint Protection with JWT

The main components used:

  • Go as the backend language
  • gqlgen as the GraphQL code generator
  • gorilla/mux (optional) as the HTTP router
  • JWT (github.com/golang-jwt/jwt/v5) for token generation and validation
  • bcrypt for password hashing

Database and User Structure

For simplicity, we’ll use in-memory storage in the form of a map (map[string]*User). In the real world, you can easily swap this out for Postgres or MongoDB.

go
1type User struct {
2    ID       string
3    Username string
4    Password string // Hashed!
5}

In simple terms, the storage structure looks like this:

FieldTypeDescription
IDstringUUID
UsernamestringUnique, used for login
PasswordstringAlready hashed with bcrypt

GraphQL Schema

For registration and login, two simple mutations are enough:

graphql
 1type User {
 2    id: ID!
 3    username: String!
 4}
 5
 6type AuthPayload {
 7    token: String!
 8    user: User!
 9}
10
11type Mutation {
12    register(username: String!, password: String!): AuthPayload!
13    login(username: String!, password: String!): AuthPayload!
14}
15
16type Query {
17    me: User
18}

The Registration Process

Steps:

  1. Input the user’s username & password.
  2. Check whether the username is already taken.
  3. Hash the password: bcrypt.
  4. Save the new user to the database.
  5. Generate a JWT token.
  6. Return the token & user data.
go
 1func (r *mutationResolver) Register(ctx context.Context, username string, password string) (*model.AuthPayload, error) {
 2    if _, exists := users[username]; exists {
 3        return nil, errors.New("username already exists")
 4    }
 5    hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
 6    if err != nil {
 7        return nil, err
 8    }
 9    user := &User{
10        ID:       uuid.NewString(),
11        Username: username,
12        Password: string(hashed),
13    }
14    users[username] = user
15    token, err := generateJWT(user)
16    if err != nil {
17        return nil, err
18    }
19    return &model.AuthPayload{
20        Token: token,
21        User:  &model.User{ID: user.ID, Username: user.Username},
22    }, nil
23}

The Login Process

The flow is very similar; only the password validation step is replaced with a hash compare.

go
 1func (r *mutationResolver) Login(ctx context.Context, username string, password string) (*model.AuthPayload, error) {
 2    user, exists := users[username]
 3    if !exists {
 4        return nil, errors.New("invalid credentials")
 5    }
 6    if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
 7        return nil, errors.New("invalid credentials")
 8    }
 9    token, err := generateJWT(user)
10    if err != nil {
11        return nil, err
12    }
13    return &model.AuthPayload{
14        Token: token,
15        User:  &model.User{ID: user.ID, Username: user.Username},
16    }, nil
17}

JWT Generation & Validation

A JWT is created during register/login and validated every time a protected endpoint is accessed.

The generateJWT() function:

go
1func generateJWT(user *User) (string, error) {
2    claims := jwt.MapClaims{
3        "sub": user.ID,
4        "username": user.Username,
5        "exp": time.Now().Add(time.Hour * 24).Unix(),
6    }
7    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
8    return token.SignedString([]byte(SECRET_KEY))
9}

The parseJWT() function:

go
 1func parseJWT(tokenStr string) (*User, error) {
 2    token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
 3        return []byte(SECRET_KEY), nil
 4    })
 5    if err != nil || !token.Valid {
 6        return nil, errors.New("invalid token")
 7    }
 8    claims, ok := token.Claims.(jwt.MapClaims)
 9    if !ok {
10        return nil, errors.New("invalid token claims")
11    }
12    username := claims["username"].(string)
13    user, exists := users[username]
14    if !exists {
15        return nil, errors.New("user not found")
16    }
17    return user, nil
18}

Endpoint Protection Middleware

In gqlgen, we inject the user into the context, and then in specific resolvers we can check authentication.

go
 1func AuthMiddleware() func(ctx context.Context) context.Context {
 2    return func(ctx context.Context) context.Context {
 3        token := extractBearerToken(ctx)
 4        if token == "" {
 5            return ctx
 6        }
 7        user, err := parseJWT(token)
 8        if err != nil {
 9            return ctx
10        }
11        return context.WithValue(ctx, "user", user)
12    }
13}

Then, for example, in the me query:

go
1func (r *queryResolver) Me(ctx context.Context) (*model.User, error) {
2    user := ctx.Value("user")
3    if user == nil {
4        return nil, errors.New("unauthenticated")
5    }
6    u := user.(*User)
7    return &model.User{ID: u.ID, Username: u.Username}, nil
8}

Flow Diagram

Let’s illustrate the registration and login flow using Mermaid:

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

Client Simulation

For example, here are the GraphQL mutations being posted:

1. Registration

graphql
1mutation {
2  register(username: "andi", password: "rahasia") {
3    token
4    user { id username }
5  }
6}

Response:

json
1{
2  "data": {
3    "register": {
4      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI... (JWT)",
5      "user": { "id": "c780...", "username": "andi" }
6    }
7  }
8}

2. Login

graphql
1mutation {
2  login(username: "andi", password: "rahasia") {
3    token
4    user { id username }
5  }
6}

3. Querying Your Own Data (Protected)

Include the header: Authorization: Bearer {token}

graphql
1query {
2  me { id username }
3}

Comparison Table: REST vs GraphQL + JWT

FeatureREST + JWTGraphQL + JWT
Registration/Login Endpoint/register, /login1 endpoint (/graphql), different mutations
Auth ProtectionMiddleware per routeMiddleware per field/resolver
ExtensibilityNeeds new endpointJust add it to the schema
TransportJSONGraphQL Query

Security Notes

  • Never store passwords without hashing.
  • JWTs should be given a reasonable expiry (exp).
  • Use HTTPS!
  • Validate user input (XSS, SQLI, etc.).

Conclusion

With a pattern like the one above, you can deploy a GraphQL + JWT authentication application quickly and securely, taking advantage of Go’s type safety plus gqlgen’s performance. In the real world, you simply add a connection to Postgres or MongoDB and apply stricter security measures such as rate limiting and monitoring.

Hopefully this simple case study of JWT registration & login with gqlgen can serve as a foundation for implementing modern authentication in your next project.

Happy hacking! 🚀


Full Demo Code:
github.com/youruser/gqlgen-jwt-example (dummy repository, please replace it with your own repo)

Related Articles

💬 Comments