Hone logo
Hone
Problems

Secure Your Go API: Implementing User Authentication

Building secure web applications is paramount, and user authentication is a fundamental pillar of that security. This challenge requires you to implement a robust authentication system for a Go-based API, enabling users to register, log in, and protect certain API endpoints. This is a common and crucial task for any developer building real-world web services.

Problem Description

Your task is to create a basic Go web server that handles user authentication. This involves:

  1. User Registration: Allow new users to register with a username and password.
  2. User Login: Allow registered users to log in using their credentials.
  3. Secure Endpoint: Implement a protected endpoint that can only be accessed by authenticated users.
  4. Token-Based Authentication: Use JSON Web Tokens (JWT) for managing user sessions and authorizing access to protected resources.

Key Requirements:

  • Password Hashing: Passwords must be securely hashed before storage. Use a strong hashing algorithm like bcrypt.
  • JWT Generation and Validation: Upon successful login, generate a JWT containing user information (e.g., username). This token should be returned to the client. For protected endpoints, the server must validate the incoming JWT.
  • In-Memory User Storage: For this challenge, you can store user credentials (username and hashed password) in memory. In a production environment, you would use a database.
  • HTTP Methods:
    • POST /register: For user registration.
    • POST /login: For user login.
    • GET /protected: A protected endpoint.

Expected Behavior:

  • Registration: A successful registration should store the user's username and hashed password. The API should respond with a success message.
  • Login: A successful login should return a JWT. An unsuccessful login (invalid credentials) should return an appropriate error.
  • Protected Endpoint:
    • If a request to /protected includes a valid JWT in the Authorization header (e.g., Authorization: Bearer <token>), the request should be processed, and the user should receive a success message indicating they are authenticated.
    • If the request lacks a valid JWT or the token is invalid/expired, the API should return an unauthorized error (e.g., HTTP 401).

Edge Cases:

  • Attempting to register with an existing username.
  • Attempting to log in with incorrect credentials.
  • Accessing a protected endpoint without a token.
  • Accessing a protected endpoint with an invalid or expired token.

Examples

Example 1: User Registration

Input:
POST /register
Content-Type: application/json

{
  "username": "alice",
  "password": "securepassword123"
}

Output:
HTTP/1.1 201 Created
Content-Type: application/json

{
  "message": "User 'alice' registered successfully"
}

Explanation: The API receives a POST request to /register with a username and password. It hashes the password, stores the user's credentials (username and hashed password) in memory, and returns a success message.

Example 2: User Login (Successful)

Input:
POST /login
Content-Type: application/json

{
  "username": "alice",
  "password": "securepassword123"
}

Output:
HTTP/1.1 200 OK
Content-Type: application/json

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFsaWNlIiwibmFtZSI6ImFsaWNlIiwiZXhwIjoxNzE4MjYyMzk3fQ.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" // Example JWT (actual will vary)
}

Explanation: The API receives a POST request to /login. It verifies the provided password against the stored hashed password for "alice". Upon successful verification, it generates a JWT containing the username and returns it.

Example 3: Accessing Protected Endpoint (Authenticated)

Input:
GET /protected
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFsaWNlIiwibmFtZSI6ImFsaWNlIiwiZXhwIjoxNzE4MjYyMzk3fQ.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx // (Use token from previous login)

Output:
HTTP/1.1 200 OK
Content-Type: application/json

{
  "message": "Welcome, alice! You are authenticated."
}

Explanation: The API receives a GET request to /protected with a valid JWT in the Authorization header. It validates the token, extracts the username, and returns a personalized welcome message.

Example 4: Accessing Protected Endpoint (Unauthorized)

Input:
GET /protected

Output:
HTTP/1.1 401 Unauthorized
Content-Type: application/json

{
  "message": "Unauthorized: Missing or invalid token"
}

Explanation: The API receives a GET request to /protected without an Authorization header. It rejects the request and returns an unauthorized error.

Constraints

  • Hashing Algorithm: Use golang.org/x/crypto/bcrypt for password hashing.
  • JWT Library: You can use a popular Go JWT library like github.com/golang-jwt/jwt/v4.
  • Token Expiration: JWTs should have a reasonable expiration time (e.g., 1 hour).
  • Secret Key: A strong, secret key should be used for signing JWTs. This should be managed securely (e.g., via environment variables in production).
  • Concurrency: Your in-memory storage should be safe for concurrent access.

Notes

  • Consider using Go's standard net/http package for handling HTTP requests and routing.
  • You'll need to parse JSON request bodies and construct JSON responses.
  • Think about how to handle errors gracefully and return informative messages to the client.
  • For JWTs, you'll need to define the claims that will be stored in the token.
  • The secret key for JWT signing is critical for security. For this challenge, you can define it as a constant, but be aware of best practices for managing secrets in real applications.
Loading editor...
go