Add Basic Authentication to NanoHTTPD: A Minimal Implementation Guide
NanoHTTPD has no built-in authentication. Implement Basic Auth by overriding the serve method, checking the Authorization header, and decoding base64 credentials manually. Always use HTTPS since Basic Auth provides no encryption.
06 Feb 2026, 06:51 UTC

The Problem: NanoHTTPD Has No Built-In Auth
NanoHTTPD is a lightweight Java HTTP server library, but it does not include authentication features. You must implement Basic Auth yourself by parsing the Authorization header manually in your serve method.
Working Configuration
Here is a complete NanoHTTPD subclass that enforces Basic Authentication:
import fi.iki.harri.niohttpd.NanoHTTPD;
import java.util.Base64;
public class SecureNanoHTTPD extends NanoHTTPD {
private static final String REALM = "SecureArea";
private static final String USERNAME = "admin";
private static final String PASSWORD = "secret123";
public SecureNanoHTTPD(int port) throws Exception {
super(port);
}
@Override
public Response serve(IHTTPSession session) {
String authHeader = session.getHeaders().get("authorization");
if (authHeader == null || !authHeader.startsWith("Basic ")) {
Response resp = Response.newFixedLengthResponse(Response.Status.UNAUTHORIZED,
"text/plain", "Authentication required");
resp.addHeader("WWW-Authenticate", "Basic realm=\"SecureArea\"");
return resp;
}
String base64Creds = authHeader.substring(6);
String creds = new String(Base64.getDecoder().decode(base64Creds));
String[] parts = creds.split(":", 2);
if (parts.length != 2) {
return Response.newFixedLengthResponse(Response.Status.BAD_REQUEST,
"text/plain", "Invalid auth format");
}
String username = parts[0];
String password = parts[1];
if (!USERNAME.equals(username) || !PASSWORD.equals(password)) {
Response resp = Response.newFixedLengthResponse(Response.Status.UNAUTHORIZED,
"text/plain", "Invalid credentials");
resp.addHeader("WWW-Authenticate", "Basic realm=\"SecureArea\"");
return resp;
}
return Response.newFixedLengthResponse("Hello, " + username + "! Protected content here.");
}
public static void main(String[] args) throws Exception {
SecureNanoHTTPD server = new SecureNanoHTTPD(8080);
server.start();
System.out.println("Server running on port 8080");
}
}
Key Implementation Details
- Authorization Header Check: The
servemethod receives every request. Check for theauthorizationheader and verify it starts withBasic. - Base64 Decoding: Basic Auth sends credentials as
base64(username:password). Decode and split on the colon to extract username and password. - 401 Response: When auth is missing or invalid, return
Response.Status.UNAUTHORIZED. Add aWWW-Authenticateheader so browsers and clients know which realm to use.
Response resp = Response.newFixedLengthResponse(Response.Status.UNAUTHORIZED,
"text/plain", "Auth required");
resp.addHeader("WWW-Authenticate", "Basic realm=\"myrealm\"");
return resp;
Testing the Implementation
Run the server locally, then test with curl:
# Test without auth - should return 401
curl -i http://localhost:8080/
# Test with valid auth - should return 200
curl -i -u admin:secret123 http://localhost:8080/
# Test with invalid auth - should return 401
curl -i -u admin:wrongpass http://localhost:8080/
Practical Limits and Common Mistakes
Credential Security
Never store passwords in plain text. Use an environment variable or properties file instead:
private static final String PASSWORD = System.getenv("NANO_PASSWORD");
Performance Impact
The serve method is called for every request. Avoid expensive operations such as database lookups on each request. Cache validated credentials if possible.
HTTPS is Mandatory
Base64 encoding is not encryption. Without TLS, credentials transmit in easily decodable form. Always use HTTPS for any production deployment.
Realm Segmentation
Use different realms for different API sections:
// Admin realm
resp.addHeader("WWW-Authenticate", "Basic realm=\"Admin Area\"");
// API realm
resp.addHeader("WWW-Authenticate", "Basic realm=\"API Access\"");
Common Implementation Errors
- Returning 200 Instead of 401: Forgetting to return
UNAUTHORIZEDmeans clients will not retry with credentials. - Incomplete Header Parsing: Not handling malformed base64 or a missing colon separator can cause crashes.
- Case-Sensitive Header Lookup: HTTP headers are case-insensitive. Use
session.getHeaders().get("authorization")in lowercase to be safe.
Verification Checklist
- Server returns 401 without credentials
- Server returns 401 with invalid credentials
- Server returns 200 with valid credentials
WWW-Authenticateheader present in 401 responses- Credentials decode correctly when tested with a known base64 string
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.