Basic Authentication
Learn how to implement Basic Authentication in your Goa API
Goa provides robust security features that allow you to protect your APIs at multiple levels. Whether you need basic authentication, API keys, JWT tokens, or OAuth2, Goa’s security DSL makes it straightforward to implement secure endpoints.
This section will guide you through Goa’s security features, from basic concepts to advanced implementations. We’ll cover each authentication method in detail and provide best practices for securing your APIs.
Security in Goa is implemented through security schemes - reusable definitions that specify how authentication and authorization should work. Think of these schemes as templates that define how your API will verify the identity of clients trying to access your endpoints.
These schemes can be applied at three different levels, providing flexible security configuration:
API level: When applied at the API level, the security scheme becomes the default for all endpoints in your API. This is useful when you want consistent security across your entire API.
Service level: Services can override the API-level security or define their own security if none is defined at the API level. This allows you to have different security requirements for different groups of related endpoints.
Method level: Individual methods (endpoints) can override both API and service level security. This gives you the finest level of control, allowing specific endpoints to use different security schemes or no security at all.
Goa supports several common security mechanisms through dedicated DSL functions. Each scheme is designed for specific use cases:
Basic authentication is one of the simplest forms of API security, where clients provide a username and password. While simple, it should only be used over HTTPS to ensure credentials are encrypted during transmission.
Learn more about Basic Authentication →
var BasicAuth = BasicAuthSecurity("basic", func() {
Description("Basic authentication using username and password")
})
API keys provide a simple way to authenticate clients using a single token. They’re commonly passed in headers or query parameters. This method is popular for public APIs that need to track usage or implement rate limiting.
Learn more about API Key Authentication →
var APIKeyAuth = APIKeySecurity("api_key", func() {
Description("Secures endpoint by requiring an API key.")
})
JWT authentication is ideal for stateless authentication using signed tokens that can carry claims. JWTs are perfect for microservices architectures where you need to pass authentication and authorization information between services.
Learn more about JWT Authentication →
var JWTAuth = JWTSecurity("jwt", func() {
Description("Secures endpoint by requiring a valid JWT token.")
Scope("api:read", "Read access to API")
Scope("api:write", "Write access to API")
})
OAuth2 provides a comprehensive solution for delegated authorization. It’s ideal when you need to allow third-party applications to access your API on behalf of your users.
Learn more about OAuth2 Authentication →
var OAuth2 = OAuth2Security("oauth2", func() {
Description("OAuth2 authentication")
ImplicitFlow("/authorization")
Scope("api:write", "Write access")
Scope("api:read", "Read access")
})
Let’s examine a complete example that demonstrates how security schemes can be applied at different levels. This example shows the flexibility of Goa’s security system and how different security requirements can be combined:
package design
import (
. "goa.design/goa/v3/dsl"
)
// Define our security schemes
var BasicAuth = BasicAuthSecurity("basic", func() {
Description("Basic authentication")
})
var APIKeyAuth = APIKeySecurity("api_key", func() {
Description("Secures endpoint by requiring an API key.")
})
var JWTAuth = JWTSecurity("jwt", func() {
Description("Secures endpoint by requiring a valid JWT token.")
})
// Apply security at the API level
var _ = API("hierarchy", func() {
Title("Security Example API")
Description("This API demonstrates the effect of using Security at the API, " +
"Service or Method levels")
// Set basic auth as the default security scheme for all endpoints
Security(BasicAuth)
})
This service inherits the API-level basic authentication. Notice how the payload must include username and password fields:
var _ = Service("default_service", func() {
Method("default", func() {
Description("The default service default_method is secured with basic " +
"authentication")
// Define the expected payload for basic auth
Payload(func() {
Username("username") // Special DSL for username field
Password("password") // Special DSL for password field
Required("username", "password")
})
HTTP(func() { GET("/default") })
})
})
This service demonstrates how to override security at both service and method levels, showing the flexibility of Goa’s security system:
var _ = Service("api_key_service", func() {
Description("The svc service is secured with API key based authentication")
HTTP(func() { Path("/svc") })
// Override API-level security for this entire service
Security(APIKeyAuth)
Method("default", func() {
// This method uses the service-level API key security
Payload(func() {
APIKey("api_key", "key", String, func() {
Description("API key used for authentication")
})
Required("key")
})
HTTP(func() { GET("/default") })
})
Method("secure", func() {
// Override service-level security for this specific method
Security(JWTAuth)
Description("This method requires a valid JWT token.")
Payload(func() {
Token("token", String, func() {
Description("JWT used for authentication")
})
Required("token")
})
HTTP(func() { GET("/secure") })
})
Method("unsecure", func() {
Description("This method is not secured.")
// Remove all security requirements for this method
NoSecurity()
})
})
Sometimes you need to make certain endpoints publicly accessible, such as health checks
or public documentation endpoints. The NoSecurity()
function removes any security
requirements from a method:
Method("health", func() {
Description("Public health check endpoint")
NoSecurity()
HTTP(func() { GET("/health") })
})
When implementing security in your Goa services, follow these guidelines for the best results:
Learn more about Security Best Practices →
NoSecurity()
explicitly when making endpoints publicGoa automatically generates the necessary code to enforce your security requirements. The generated code provides several benefits:
When you define security requirements in your Goa design, the framework generates comprehensive security middleware that handles all the essential authentication tasks. This middleware automatically extracts credentials from incoming requests, validates them against your defined requirements, manages authentication errors, and enforces any scope requirements you’ve specified for OAuth2 or JWT authentication.
The security definitions are also reflected in the generated OpenAPI/Swagger documentation, making it easy for API consumers to understand your authentication requirements. The documentation clearly outlines what authentication methods are supported, what scopes are required for different endpoints, the expected format for credentials, and what error responses clients should handle.
To ensure type safety and maintainability, Goa generates strongly typed interfaces and structures for working with security. This includes type-safe interfaces for implementing security handlers, strongly typed credential structures that match your security schemes, helper functions for accessing security information from the request context, and pre-defined error types for handling various security-related failures. This type safety helps catch potential security implementation issues at compile time rather than runtime.
This means you can focus on defining your security requirements declaratively, while Goa handles the implementation details. The generated code is type-safe and follows Go best practices.