Basic Authentication is a simple authentication scheme built into the HTTP protocol. While it’s one of the simplest forms of authentication, it’s still widely used, especially for internal APIs or development environments.
When using Basic Authentication:
Authorization: Basic base64(username:password)
First, define your Basic Auth security scheme in your design package:
package design
import (
. "goa.design/goa/v3/dsl"
)
// BasicAuth defines our security scheme
var BasicAuth = BasicAuthSecurity("basic", func() {
Description("Use your username and password to access the API")
})
You can apply Basic Auth at different levels:
// API level - applies to all services and methods
var _ = API("secure_api", func() {
Security(BasicAuth)
})
// Service level - applies to all methods in the service
var _ = Service("secure_service", func() {
Security(BasicAuth)
})
// Method level - applies only to this method
Method("secure_method", func() {
Security(BasicAuth)
})
For methods that use Basic Auth, you need to define the payload to include username and password fields:
Method("login", func() {
Security(BasicAuth)
Payload(func() {
// These special DSL functions are recognized by Goa
Username("username", String, "Username for authentication")
Password("password", String, "Password for authentication")
Required("username", "password")
})
Result(String)
HTTP(func() {
POST("/login")
// Response defines what happens after successful authentication
Response(StatusOK)
})
})
When Goa generates the code, you’ll need to implement a security handler. Here’s an example:
// SecurityBasicAuthFunc implements the authorization logic for Basic Auth
func (s *service) BasicAuth(ctx context.Context, user, pass string) (context.Context, error) {
// Implement your authentication logic here
if user == "admin" && pass == "secret" {
// Authentication successful
return ctx, nil
}
// Authentication failed
return ctx, basic.Unauthorized("invalid credentials")
}
Always Use HTTPS Basic Auth sends credentials base64 encoded (not encrypted). Always use HTTPS to protect credentials in transit.
Secure Password Storage
Rate Limiting Implement rate limiting to prevent brute force attacks:
var _ = Service("secure_service", func() {
Security(BasicAuth)
// Add rate limiting annotation
Meta("ratelimit:limit", "60")
Meta("ratelimit:window", "1m")
})
Error Messages Don’t reveal whether the username or password was incorrect. Use generic messages:
return ctx, basic.Unauthorized("invalid credentials")
Logging Log authentication attempts but never log passwords:
func (s *service) BasicAuth(ctx context.Context, user, pass string) (context.Context, error) {
// Good: Log only the username and result
log.Printf("Authentication attempt for user: %s", user)
// Bad: Never do this
// log.Printf("Password attempt: %s", pass)
}
Here’s a complete example showing how to implement Basic Auth in a Goa service:
package design
import (
. "goa.design/goa/v3/dsl"
)
var BasicAuth = BasicAuthSecurity("basic", func() {
Description("Basic authentication for API access")
})
var _ = API("secure_api", func() {
Title("Secure API Example")
Description("API demonstrating Basic Authentication")
// Apply Basic Auth to all endpoints by default
Security(BasicAuth)
})
var _ = Service("secure_service", func() {
Description("A secure service requiring authentication")
Method("getData", func() {
Description("Get protected data")
// Define the security requirements
Security(BasicAuth)
// Define the payload (credentials will be added automatically)
Payload(func() {
// Add any additional payload fields here
Field(1, "query", String, "Search query")
})
// Define the result
Result(ArrayOf(String))
// Define the HTTP transport
HTTP(func() {
GET("/data")
Response(StatusOK)
Response(StatusUnauthorized, func() {
Description("Invalid credentials")
})
})
})
// Example of a public endpoint
Method("health", func() {
Description("Health check endpoint")
NoSecurity()
Result(String)
HTTP(func() {
GET("/health")
})
})
})
Goa generates several components for Basic Auth:
Security Types
Middleware
OpenAPI Documentation
If credentials aren’t being sent, check:
Authorization
header formatCommon causes:
Security()
in designFor browser-based clients, ensure proper CORS configuration:
var _ = Service("secure_service", func() {
HTTP(func() {
// Allow credentials in CORS
Meta("cors:expose_headers", "Authorization")
Meta("cors:allow_credentials", "true")
})
})