Using Zitadel as an Identity Authentication Center for Go Projects

Introduction⌗
For teams that have multiple projects to develop, each requiring user authentication and authorization, Zitadel is a good solution. You don’t need to redevelop authentication and authorization logic for each project.
Zitadel supports multi-tenancy and customization of authentication pages with branding. As a backend developer, we only need to integrate the official SDK!
If you’re not familiar with Zitadel, you can check out my article Open Source Identity Authentication and Authorization Solutions.
Concepts⌗
Zitadel issues two types of tokens for logged-in users:
- Access Token
- ID Token
Access Tokens are further divided into Bearer Tokens and JSON Web Tokens, with the difference being whether they carry some basic data.
ID Tokens are essentially also a type of JWT, but they contain more comprehensive user information, such as user Metadata, Roles, Permissions, and other data.
Token Verification⌗
Whether it’s an Access Token or an ID Token, to confirm a user’s identity, you must verify that the token carried in the request was issued by the specified Issuer and has not been tampered with.
To achieve this, you need to use an RSA Public Key for signature verification. This logic has a specific term called Introspection, and there are two ways to verify tokens:
- The resource server requests the identity authentication server’s Introspection Endpoint
- The resource server caches the identity authentication server’s JWK Set (i.e., the RSA Public Key) and performs verification locally, which is known as: Self-Introspection
However, these two methods have their respective advantages and disadvantages, and you need to decide which one to use based on your specific business requirements.
The method of verifying tokens through the Introspection Endpoint can achieve a high degree of uniformity. When a user logs out or a token is revoked, the resource server can learn that the token has become invalid from the identity authentication server’s response. But the problem with this approach is also quite prominent: each request needs to be verified at the Introspection Endpoint, which can increase API response time by tens to hundreds of milliseconds!
The Self-Introspection method avoids performance issues by caching the identity authentication service’s JWK Set and then verifying the token locally, reducing unnecessary requests and having no impact on response time. However, the problem is also quite prominent: the token cannot be revoked during its validity period!
So, if your business prioritizes security, choose verification through the Introspection Endpoint. If you prioritize performance, choose Self-Introspection!
Project Integration⌗
- Create a project in Zitadel, and create both Web and API applications for the project:
- Web application settings are as follows:
- Create a Key under the API application
After creation, you’ll be prompted to download a JSON file for the key. This file is what middleware.OSKeyPath()
points to, and you can set the path to this key using the ZITADEL_KEY_PATH
environment variable.
Additionally, the framework I’m using is fiber, which is based on fastrouter and differs somewhat from Go’s built-in router!
Introspection⌗
go get github.com/zitadel/zitadel-go/v2
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/gofiber/fiber/v2/middleware/adaptor"
http_mw "github.com/zitadel/zitadel-go/v2/pkg/api/middleware/http"
"github.com/zitadel/zitadel-go/v2/pkg/client/middleware"
)
func profile(ctx *fiber.Ctx) error {
return ctx.SendString("Success")
}
func main() {
app := fiber.New()
api := app.Group("/api")
introspection, err := http_mw.NewIntrospectionInterceptor("https://zitadel.local", middleware.OSKeyPath())
if err != nil {
log.Error(err)
}
api.Use(adaptor.HTTPMiddleware(introspection.Handler))
api.Get("/user/profile", profile).Name("User profile")
log.Fatal(app.Listen(":3000"))
}
Code explanation:
- Line 20: Declare the interceptor
- Line 25: Convert the middleware to a fiber Handler
Currently, the official SDK does not yet support retrieving user information. At the application layer, you can only know whether the token is valid. Although there is user information in the response from
/oauth/v2/introspect
, it is not processed in the SDK!
Self-Introspection⌗
package main
import (
"net/http"
"strings"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/zitadel/oidc/pkg/client/rp"
"github.com/zitadel/oidc/pkg/oidc"
"github.com/zitadel/oidc/pkg/op"
)
var (
keySet oidc.KeySet
verifier op.AccessTokenVerifier
)
func init() {
keySet = rp.NewRemoteKeySet(http.DefaultClient, "https://zitadel.local/oauth/v2/keys")
verifier = op.NewAccessTokenVerifier("https://zitadel.local", keySet)
}
func authHandler() fiber.Handler {
return func(ctx *fiber.Ctx) (err error) {
token := ctx.Get("Authorization")
token = strings.TrimPrefix(token, oidc.PrefixBearer)
claims, err := op.VerifyAccessToken(context.Background(), token, verifier)
if err != nil {
log.Error(err)
return ctx.JSON(http.UnAuthenticated("Authentication failed"))
}
ctx.Locals("user", claims)
return ctx.Next()
}
}
func main() {
app := fiber.New()
api := app.Group("/api")
api.Use(authHandler())
api.Get("/user/profile", profile).Name("User profile")
log.Fatal(app.Listen(":3000"))
}
Code explanation:
- Line 19: Declare Remote Key Set, note that this variable is global and stateful, used to cache the Key Set
- Line 20: Declare the Token verifier
- Line 28: Verify whether the Token is valid and get Claims
- Line 34: Put Claims into fiber.Ctx for subsequent logic to retrieve user information
Conclusion⌗
With this, the integration of Zitadel in a Go project is complete. For small projects, adopting this approach can further reduce development cycles and deliver projects earlier!
Going a step further, you could implement user authentication and authorization on an API Gateway or other proxy server, such as Traefik, and the backend can obtain user information through agreed-upon request headers!
I hope this is helpful, Happy hacking…