Welcome to the first lesson of this course on API authentication methods with Go. In this lesson, we will explore how to access protected routes using API keys. Understanding API authentication is crucial because it ensures that only authorized users can interact with specific parts of an API. Among various methods, API keys are a common way to authenticate requests. They serve as a simple passkey to gain access to protected routes. By the end of this lesson, you will know how to integrate API keys into your requests to securely access API endpoints using Go.
Authentication in RESTful APIs protects data by ensuring only authorized users can access resources and perform certain actions. This process verifies the identity of a client attempting to access a resource.
Common methods of authentication in RESTful APIs include:
- API Keys: A unique token generated for each client to grant access to the API. It acts like a secret passcode.
- Sessions: Involves storing authentication details on the server side, typically using a session ID to maintain state between requests.
- JWT (JSON Web Tokens): Compact tokens that verify the identity of the client and carry additional claims.
- Other Methods: Authentication methods like OAuth, which provides secure delegated access, and Basic Authentication, using encoded usernames and passwords, are also prevalent but will not be covered in this course.
Each of these methods varies in complexity and security levels, offering different benefits depending on the use case. In this lesson, we will focus specifically on integrating API keys into your requests.
Before diving into the specifics of API keys, let's take a moment to understand HTTP headers. HTTP headers function like envelopes, carrying additional information about the request or response. They can include various kinds of data, such as:
- Content Type: Specifies the media type of the resource, e.g.,
application/json
. - User Agent: Provides information about the client software, useful for analytics.
- Authentication Details: Credentials like API keys to access protected resources.
Headers can serve different purposes, such as specifying the preferred language (Accept-Language
) or content type. In Go, using the net/http
package, you can easily add headers to a request by setting them in the request object:
Go1package main 2 3import ( 4 "fmt" 5 "net/http" 6) 7 8func main() { 9 req, err := http.NewRequest("GET", "http://example.com", nil) 10 if err != nil { 11 fmt.Println(err) 12 return 13 } 14 req.Header.Set("Content-Type", "application/json") 15 16 // Use the request in an HTTP client 17 client := &http.Client{} 18 resp, err := client.Do(req) 19 if err != nil { 20 fmt.Println(err) 21 return 22 } 23 defer resp.Body.Close() 24 25 fmt.Println("Response status:", resp.Status) 26}
In this example, the Content-Type
header is added to the request to specify that the client expects JSON data. Simply use the Header.Set
method to include any required headers in your request.
API keys are typically provided by the service you wish to access. After registering for an account, you might receive this key via email, or it could be available in your account settings on the provider's website. Once obtained, you can assign the API key as a straightforward variable in your code for easy use when making requests:
Go1package main 2 3var API_KEY = "123e4567-e89b-12d3-a456-426614174000" // ⚠️ Warning: Never hardcode API keys in your code.
⚠️ Warning: Hardcoding API keys is a security risk. Never store credentials in your source code—especially if sharing or deploying it.
To enhance security and manage API keys more effectively, you can use environment variables. By storing keys in an environment variable, you keep your credentials private and secure. Here's how you can achieve this in Go:
-
Install the
godotenv
package:Bash1go get github.com/joho/godotenv
-
Set the environment variable in your system or use a
.env
file:Plain text1API_KEY=123e4567-e89b-12d3-a456-426614174000
-
Load the environment file with
godotenv
and use theos
package to read the environment variable in your Go program:Go1package main 2 3import ( 4 "fmt" 5 "os" 6 "github.com/joho/godotenv" 7) 8 9func main() { 10 err := godotenv.Load() 11 if err != nil { 12 fmt.Println("Error loading .env file") 13 return 14 } 15 16 apiKey := os.Getenv("API_KEY") 17 if apiKey == "" { 18 fmt.Println("API key not set") 19 return 20 } 21 22 fmt.Println("API key loaded successfully!") 23}
This approach keeps your API keys secure and simplifies management across different environments, such as development and production.
Imagine that now our API endpoints, such as /todos
, are protected and require a valid API key to access. Without the key, any request will return a 401 Unauthorized
error. This is where HTTP headers come into play. We can include the API key in the request headers to authenticate successfully:
Go1package main 2 3import ( 4 "fmt" 5 "net/http" 6 "os" 7) 8 9func main() { 10 apiKey := os.Getenv("API_KEY") 11 if apiKey == "" { 12 fmt.Println("API key not set") 13 return 14 } 15 16 req, err := http.NewRequest("GET", "http://localhost:8000/todos", nil) 17 if err != nil { 18 fmt.Println(err) 19 return 20 } 21 req.Header.Set("X-API-Key", apiKey) 22 23 client := &http.Client{} 24 resp, err := client.Do(req) 25 if err != nil { 26 fmt.Println(err) 27 return 28 } 29 defer resp.Body.Close() 30 31 fmt.Println("Response status:", resp.Status) 32}
In this example, the API key is included in the headers using the X-API-Key
custom header. The X-API-Key
header is commonly used by APIs to specifically indicate the inclusion of the API key, allowing the server to verify the request as authorized. By placing the API key within the headers, the server can verify your request as authorized.
Here's the full implementation, including loading the API key securely from an environment variable and handling potential errors during the request:
Go1package main 2 3import ( 4 "fmt" 5 "io" 6 "net/http" 7 "os" 8) 9 10func main() { 11 // Retrieve the API key from the environment variables 12 apiKey := os.Getenv("API_KEY") 13 if apiKey == "" { 14 fmt.Println("API key not set") 15 return 16 } 17 18 // Base URL for the API 19 baseURL := "http://localhost:8000" 20 21 // Create a new request 22 req, err := http.NewRequest("GET", fmt.Sprintf("%s/todos", baseURL), nil) 23 if err != nil { 24 fmt.Println("Error creating request:", err) 25 return 26 } 27 28 // Set the API key in headers 29 req.Header.Set("X-API-Key", apiKey) 30 31 // Create an HTTP client and send the request 32 client := &http.Client{} 33 resp, err := client.Do(req) 34 if err != nil { 35 fmt.Println("Error making request:", err) 36 return 37 } 38 defer resp.Body.Close() 39 40 // Check for HTTP errors 41 if resp.StatusCode != http.StatusOK { 42 bodyBytes, _ := io.ReadAll(resp.Body) 43 fmt.Printf("An HTTP error occurred: %s\n", resp.Status) 44 fmt.Printf("Error: %s\n", string(bodyBytes)) 45 return 46 } 47 48 // Print success message and JSON response 49 bodyBytes, _ := io.ReadAll(resp.Body) 50 fmt.Println("Accessed protected endpoint successfully!") 51 fmt.Println(string(bodyBytes)) 52}
In this implementation, the API key is securely loaded from the environment variable and included in the headers of our request to the /todos
endpoint. This method ensures a secure and effective way to access protected routes using API keys.
Before diving into implementation, here are some important security measures:
- Never commit API keys to public repositories.
- Use environment variables instead of storing keys in code.
- Rotate API keys periodically and revoke unused ones.
- Limit API key access (e.g., IP-based restrictions, rate limits).
- Use least privilege access—ensure API keys have limited permissions, avoiding admin-level access unless necessary.
- Monitor and log API key usage to detect suspicious activity and unauthorized access attempts.
- Set expiry dates for API keys where possible to enforce periodic key rotation.
- Encrypt API keys in storage rather than storing them as plain text.
- Use an API gateway (e.g., AWS API Gateway, Kong, Apigee) to enforce authentication, rate limiting, and logging.
- Use different API keys for different environments (development, staging, production) to prevent accidental exposure.
Throughout this lesson, we delved into the mechanics of API authentication using API keys and the role of HTTP headers in transmitting authentication information. The practical example demonstrated how to construct requests with API keys and handle responses appropriately using Go. As you progress to the practice exercises, strive to experiment with different endpoints and variations of API key usage. This lesson sets the stage for future discussions on more advanced authentication methods, such as sessions and JWT, which we will explore in the following lessons. Dive into the practice exercises now to solidify your understanding of accessing protected routes with API keys using Go.