Basic Authentication
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.
How Basic Auth Works
When using Basic Authentication:
- The client combines the username and password with a colon (username:password)
- This string is then base64 encoded
- The encoded string is sent in the Authorization header:
Authorization: Basic base64(username:password)
Implementing Basic Auth in Goa
1. Define the Security Scheme
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")
})
2. Apply the Security Scheme
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)
})
3. Define the Payload
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)
})
})
4. Implement the Security Handler
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")
}
Best Practices for Basic Auth
Always Use HTTPS Basic Auth sends credentials base64 encoded (not encrypted). Always use HTTPS to protect credentials in transit.
Secure Password Storage
- Never store passwords in plain text
- Use strong hashing algorithms (like bcrypt)
- Add salt to password hashes
- Consider using a secure password management library
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) }
Example Implementation
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")
})
})
})
Generated Code
Goa generates several components for Basic Auth:
Security Types
- Types for credentials
- Error types for authentication failures
Middleware
- Extracts credentials from requests
- Calls your security handler
- Handles authentication errors
OpenAPI Documentation
- Documents security requirements
- Shows required fields
- Documents error responses
Common Issues and Solutions
1. Credentials Not Being Sent
If credentials aren’t being sent, check:
- The
Authorizationheader format - Base64 encoding
- URL encoding of special characters
2. Always Getting Unauthorized
Common causes:
- Missing
Security()in design - Incorrect implementation of security handler
- Middleware order issues
3. CORS Issues
For 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")
})
})
Next Steps
- Learn about API Key Authentication
- Explore JWT Authentication
- Understand OAuth2 Authentication
- Read about Security Best Practices