Access Control and Scoping
Access Control and Scoping
API keys should follow the principle of least privilege, granting only the minimum permissions necessary. Implement granular scoping systems that allow different keys for different purposes: read-only keys for analytics, write keys for specific resources, or admin keys for account management.
// Java implementation of scoped API keys
import java.util.*;
import java.time.Instant;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
public class ScopedAPIKeySystem {
public enum Scope {
READ_PROFILE("profile:read"),
WRITE_PROFILE("profile:write"),
READ_ORDERS("orders:read"),
WRITE_ORDERS("orders:write"),
READ_ANALYTICS("analytics:read"),
ADMIN_USERS("admin:users"),
ADMIN_BILLING("admin:billing");
private final String value;
Scope(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
public static class APIKeyScope {
private final Set<Scope> scopes;
private final Map<String, List<String>> resourceRestrictions;
private final Optional<Instant> expiresAt;
private final Optional<String> ipWhitelist;
private APIKeyScope(Builder builder) {
this.scopes = Collections.unmodifiableSet(builder.scopes);
this.resourceRestrictions = Collections.unmodifiableMap(builder.resourceRestrictions);
this.expiresAt = builder.expiresAt;
this.ipWhitelist = builder.ipWhitelist;
}
public boolean hasScope(Scope scope) {
return scopes.contains(scope);
}
public boolean canAccessResource(String resourceType, String resourceId) {
if (!resourceRestrictions.containsKey(resourceType)) {
return true; // No restrictions for this resource type
}
List<String> allowedIds = resourceRestrictions.get(resourceType);
return allowedIds.contains("*") || allowedIds.contains(resourceId);
}
public static class Builder {
private Set<Scope> scopes = new HashSet<>();
private Map<String, List<String>> resourceRestrictions = new HashMap<>();
private Optional<Instant> expiresAt = Optional.empty();
private Optional<String> ipWhitelist = Optional.empty();
public Builder withScopes(Scope... scopes) {
this.scopes.addAll(Arrays.asList(scopes));
return this;
}
public Builder restrictResource(String resourceType, String... resourceIds) {
this.resourceRestrictions.put(resourceType, Arrays.asList(resourceIds));
return this;
}
public Builder expiresAt(Instant expiresAt) {
this.expiresAt = Optional.of(expiresAt);
return this;
}
public Builder restrictToIP(String ipPattern) {
this.ipWhitelist = Optional.of(ipPattern);
return this;
}
public APIKeyScope build() {
return new APIKeyScope(this);
}
}
}
public class ScopedAPIKeyGenerator {
private final Algorithm algorithm;
public ScopedAPIKeyGenerator(String secret) {
this.algorithm = Algorithm.HMAC256(secret);
}
public String generateScopedKey(String userId, APIKeyScope scope) {
var builder = JWT.create()
.withIssuer("api.example.com")
.withSubject(userId)
.withIssuedAt(new Date())
.withClaim("scopes",
scope.scopes.stream()
.map(Scope::getValue)
.toArray(String[]::new));
// Add expiration if specified
scope.expiresAt.ifPresent(exp ->
builder.withExpiresAt(Date.from(exp)));
// Add IP restriction if specified
scope.ipWhitelist.ifPresent(ip ->
builder.withClaim("ip_whitelist", ip));
// Add resource restrictions
scope.resourceRestrictions.forEach((type, ids) ->
builder.withClaim("resource_" + type, ids));
return builder.sign(algorithm);
}
}
// Spring Security integration
@Component
public class APIKeyAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private ScopedAPIKeyValidator validator;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String apiKey = extractAPIKey(request);
if (apiKey == null) {
filterChain.doFilter(request, response);
return;
}
try {
APIKeyValidation validation = validator.validate(apiKey, request);
if (validation.isValid()) {
// Set authentication context
APIKeyAuthentication auth = new APIKeyAuthentication(
validation.getUserId(),
validation.getScopes(),
apiKey
);
SecurityContextHolder.getContext().setAuthentication(auth);
// Add usage tracking
trackAPIKeyUsage(validation.getKeyId(), request);
}
} catch (Exception e) {
logger.error("API key validation error", e);
}
filterChain.doFilter(request, response);
}
private void trackAPIKeyUsage(String keyId, HttpServletRequest request) {
// Asynchronously track usage for rate limiting and analytics
executor.execute(() -> {
UsageRecord record = new UsageRecord(
keyId,
request.getRequestURI(),
request.getMethod(),
request.getRemoteAddr(),
Instant.now()
);
usageTracker.record(record);
});
}
}
}
Effective API key management requires continuous monitoring and improvement. The next chapter explores OAuth 2.0 implementation, which builds upon these key management principles to provide more sophisticated authorization flows.## OAuth 2.0 Implementation Guide for APIs
OAuth 2.0 has become the industry standard for API authorization, enabling secure delegated access without sharing passwords. This comprehensive guide covers implementing OAuth 2.0 for your APIs, including all grant types, security considerations, and best practices. Understanding and correctly implementing OAuth 2.0 is crucial for building secure, scalable API authentication systems.