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

116 Using Context for Authentication in gqlgen

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

116 Using Context for Authentication in gqlgen

Managing authentication is an essential part of building modern APIs, including GraphQL. In the Go ecosystem, gqlgen has become one of the most popular libraries for building GraphQL APIs thanks to its flexibility and its alignment with idiomatic, native Go style. One powerful feature that is frequently used for authentication in gqlgen is context.Context. In this article, I’ll break down how to use Context for authentication in gqlgen, complete with code examples, flow simulations, and best practices that have proven themselves in production.


Why Is Context in Go So Special?

Before we get into the implementation, let’s understand why context.Context is so often used as the primary “vehicle” for storing and carrying authentication data across each request.

  • Thread-safe: Data in a Context only applies per-request (not as global mutable state).
  • Hierarchy/scoping: Each request has its own context, which can be extended (deriving child contexts).
  • Cancellation-aware: Context can be used to cancel a request (timeout/token).
  • Transparency: Data can be retrieved from anywhere as long as you hold a reference to the context.

This is exactly why, in a GraphQL API written in Go—including when using gqlgen—Context is the best place to store verified user data without sacrificing security and concurrency.


What Does the Authentication Flow Look Like in gqlgen?

In general, the authentication process in gqlgen using Context works as follows:

  1. The client sends a query/mutation along with a token (usually a JWT) in the HTTP header
  2. The server middleware reads the Authorization header
  3. The middleware verifies the token and places the user data into the context
  4. The resolver retrieves the user from the context and continues processing

Let’s illustrate this flow with a Mermaid diagram:

MERMAID
sequenceDiagram
    participant Client
    participant Server(Middleware)
    participant Application(Resolver)
    Client->>Server: Send Query + Authorization Header
    Server->>Server: Validate & parse JWT
    alt Token Valid
        Server->>Server: Put user into Context
        Server->>Application: Call Resolver (with context)
        Application->>Application: Retrieve user from Context
        Application-->>Client: Send result
    else Token Invalid
        Server-->>Client: Send Unauthorized error
    end

Step-by-Step Implementation

We can split the implementation into three main parts:

  1. Creating the Authentication Middleware
  2. Storing & Retrieving the User in Context
  3. Accessing the User in the Resolver

Let’s break each one down.

1. Creating the Authentication Middleware (HTTP Layer)

Typically, a GraphQL endpoint is served through an HTTP handler such as mux, gin, or even the standard net/http. Here we attach middleware that will check the Authorization header.

Example of a simple middleware implementation with net/http:

go
 1func AuthMiddleware(next http.Handler) http.Handler {
 2    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 3        tokenString := r.Header.Get("Authorization")
 4        if tokenString == "" {
 5            http.Error(w, "missing token", http.StatusUnauthorized)
 6            return
 7        }
 8
 9        // For example: verify the JWT and extract the user ID/email
10        user, err := verifyAndExtractUser(tokenString)
11        if err != nil {
12            http.Error(w, "invalid token", http.StatusUnauthorized)
13            return
14        }
15
16        // Store it into the context
17        ctx := context.WithValue(r.Context(), "currentUser", user)
18        r = r.WithContext(ctx)
19
20        next.ServeHTTP(w, r)
21    })
22}

Here, we read the JWT from the header, verify it with a function (for example) verifyAndExtractUser, and store the user object into the request context. The HTTP handler that wraps gqlgen will then receive a context that already contains currentUser.

A simulation table of how the Context is built up:

StepContext State
Request comes inEmpty
Auth header is readEmpty
JWT is verified-
User is foundHas currentUser
Handler is forwardedHas currentUser

2. Storing and Accessing the User in the gqlgen Context

In gqlgen, every resolver receives the context as its first argument.

First, define the user type that we will store in the context:

go
1// models/user.go
2type User struct {
3    ID    string
4    Email string
5    Role  string
6}

To retrieve the user data from the context, we create a helper function:

go
 1// auth/context.go
 2type contextKey string
 3
 4const userCtxKey = contextKey("currentUser")
 5
 6func ForContext(ctx context.Context) *User {
 7    raw, ok := ctx.Value(userCtxKey).(*User)
 8    if !ok {
 9        return nil
10    }
11    return raw
12}

In the middleware, we store it using the same key:

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

3. Accessing the User in the gqlgen Resolver

When implementing the resolver, we simply retrieve the user from the context:

go
1func (r *queryResolver) Me(ctx context.Context) (*model.User, error) {
2    user := auth.ForContext(ctx)
3    if user == nil {
4        return nil, errors.New("unauthorized")
5    }
6    return user, nil
7}

As a result, any resolver can retrieve user information from the Context without having to know the underlying authentication mechanism. Separation of concerns is preserved beautifully.


Bonus: Role-based Authorization

Once we have the user in the Context, we can extend the scenario into role-based authorization, still using Context as the source of the user.

go
1func (r *mutationResolver) DeleteUser(ctx context.Context, userId string) (bool, error) {
2    user := auth.ForContext(ctx)
3    if user == nil || user.Role != "admin" {
4        return false, errors.New("forbidden")
5    }
6    // continue processing
7    ...
8}

API Response Simulation Table

Let’s look at a few simulated scenarios:

TokenMiddlewareResolverOutput
Empty / not presentUnauthorized-401 Unauthorized
Invalid formatUnauthorized-401 Unauthorized
Valid JWT, user not found in DBUnauthorized-401 Unauthorized
Valid JWT, normal userOKuser != nilUser data
Valid JWT, user role != adminOKForbidden403 Forbidden
Valid JWT, user role = adminOKProceed/OKSuccess

Best Practices & Tips

  • Store minimal data in the Context (just the ID/email/role) for efficiency and security.
  • Use a type-safe key for the context, avoiding collisions with arbitrary string keys.
  • Separate concerns: middleware for authentication, resolvers for authorization/business logic.
  • Log login activity centrally if you need traceability.
  • Combine with dataloaders if you want to fetch user data efficiently.

Closing

Using Context for authentication in gqlgen is an idiomatic, secure, and scalable approach in the Go ecosystem. This pattern is highly battle-tested and scales from a single instance to microservices, all the way to a monolith playground.

I hope that with the code examples, simulations, and explanations above, you can implement a Go GraphQL API with strong authentication and clean separation of concerns. If you have any questions or interesting experiences, feel free to share them in the comments!

Happy hacking 🚀

Related Articles

💬 Comments