Introduction: Why Do You Need OAuth 2.0 and JWT in Go?
\nModern web applications and APIs rarely function without an authorization system. If you are writing in Go (Golang) and want to protect your endpoints, you need to understand two key technologies: OAuth 2.0 (an access delegation protocol) and JWT (JSON Web Token — a compact token format).
\nIn this article, we will break down how to implement secure authorization in Go from scratch. You will learn:
\n- \n
- How OAuth 2.0 differs from JWT and how they work together. \n
- How to generate and verify JWT tokens in Go. \n
- How to build a minimal OAuth 2.0 authorization (Client Credentials flow). \n
- How to protect a REST API using middleware. \n
This article is intended for beginner developers who are already familiar with the basics of Go and HTTP servers.
\n\nBasics: OAuth 2.0 and JWT — They Are Not the Same Thing
\nMany beginners confuse these concepts. Let's set the record straight right away.
\n\nWhat is OAuth 2.0?
\nOAuth 2.0 is an authorization protocol that allows one service (the client) to obtain limited access to the resources of another service (the server) on behalf of a user. Imagine you want to log into a website using Google. The website does not get your password — it receives a temporary token from Google that only grants access to your email (and only with your consent).
\nThe main roles in OAuth 2.0 are:
\n- \n
- Resource Owner — the owner of the data (usually the user). \n
- Client — the application that wants to gain access. \n
- Authorization Server — the server that issues tokens after verification. \n
- Resource Server — the server that stores protected data and verifies tokens. \n
What is JWT?
\nJWT (JSON Web Token) is the format of the token itself. It is a string consisting of three parts separated by dots: header.payload.signature. A JWT can contain any data (e.g., user ID, role, expiration time) and is signed with a secret key to prevent forgery.
OAuth 2.0 is not tied to JWT — the token can be any string. However, in practice, JWT is used most often because it is self-contained: the server does not need to store sessions in a database; all information can be verified on the fly.
\n\nImplementing JWT in Go: Token Generation and Verification
\nLet's start with practice. To work with JWT in Go, we will use the golang-jwt/jwt/v5 library. Install it:
go get github.com/golang-jwt/jwt/v5\n\nGenerating an Access Token
\nLet's create a function that takes a user ID and returns a signed JWT token with a lifetime of 1 hour.
\npackage main\n\nimport (\n "time"\n "github.com/golang-jwt/jwt/v5"\n)\n\nvar jwtSecret = []byte("your-secret-key") // In production, store in .env!\n\ntype Claims struct {\n UserID uint `json:"user_id"`\n jwt.RegisteredClaims\n}\n\nfunc GenerateAccessToken(userID uint) (string, error) {\n claims := Claims{\n UserID: userID,\n RegisteredClaims: jwt.RegisteredClaims{\n ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),\n IssuedAt: jwt.NewNumericDate(time.Now()),\n Issuer: "my-app",\n },\n }\n token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)\n return token.SignedString(jwtSecret)\n}\n\nVerifying and Parsing JWT
\nNow let's write a middleware that will verify the token from the Authorization: Bearer <token> header.
func ValidateToken(tokenString string) (*Claims, error) {\n token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {\n // Check that the correct signing method is used\n