Authentication and Authorization
Explore strategies for authenticating clients and authorizing access to gRPC service methods.
Authentication and Authorization is a free gRPC & High Performance APIs lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the gRPC & High Performance APIs learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Secure Your gRPC Services
Welcome! In this lesson, we'll dive into Authentication and Authorization for gRPC services. These are crucial concepts for building secure and reliable distributed systems.
You'll learn how to verify who is accessing your services and what actions they are allowed to perform.
Authentication: Who Are You?
Authentication is the process of verifying a client's identity. Think of it like checking an ID at a club.
- It answers the question: "Are you who you say you are?"
- Common methods include API keys, JWTs (JSON Web Tokens), or OAuth tokens.
- In gRPC, these credentials are often passed as custom metadata with each request.
Authorization: What Can You Do?
Once a client is authenticated, Authorization determines what actions they are permitted to perform.
- It answers the question: "Are you allowed to do that?"
- For example, an "admin" user might be authorized to delete data, while a "guest" user can only view it.
- Authorization checks happen after successful authentication.
Why Auth & AuthZ Matter
Securing your gRPC services with proper authentication and authorization is vital:
- Prevent Unauthorized Access: Only trusted clients can interact with your services.
- Protect Sensitive Data: Ensure data is only accessed or modified by authorized entities.
- Compliance & Auditing: Meet regulatory requirements and maintain an audit trail of actions.
It's a foundational layer of security for any production system.
Clients Send Credentials
In gRPC, clients typically send authentication credentials as custom metadata in the request header.
This metadata is essentially a map of key-value pairs that travels with the RPC call. For instance, an API-Key or an Authorization header carrying a token.
The server then extracts and validates these credentials.
Server Verifies Identity
On the server side, your gRPC service needs logic to:
- Extract Credentials: Read the authentication token or API key from the incoming request's metadata.
- Validate Credentials: Check if the extracted credential is valid (e.g., compare an API key against a database, verify a JWT's signature and expiry).
- Identify Principal: If valid, identify the user or service making the request.
This process determines if the client is legitimate.
Server Checks Permissions
After a client is authenticated, the server proceeds to authorization.
This involves checking if the authenticated client (or "principal") has the necessary permissions to call the specific gRPC method requested.
- You might use roles (e.g.,
admin,user) or specific permissions associated with the client's identity. - This check often happens early in the method's execution or via an interceptor.
Attaching an API Key (Client)
Here's a simple Java client example that attaches an API-Key to a gRPC request using metadata. The Metadata class is used to build these headers.
Try running this example (you'll need the server from the next scene running first):
// auth_service.proto (simplified for context in code)
// syntax = "proto3";
// option java_multiple_files = true;
// option java_package = "com.coddykit.grpc.auth";
// option java_outer_classname = "AuthServiceProto";
// package auth;
// service AuthService {
// rpc SayHello (HelloRequest) returns (HelloResponse);
// rpc SayAdminHello (HelloRequest) returns (HelloResponse);
// }
// message HelloRequest { string name = 1; }
// message HelloResponse { string message = 1; }
package com.coddykit.grpc.auth;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;
import io.grpc.stub.StreamObserver;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class AuthClient {
private final ManagedChannel channel;
private final AuthServiceGrpc.AuthServiceStub asyncStub;
public AuthClient(String host, int port) {
channel = ManagedChannelBuilder.forAddress(host, port)
.usePlaintext() // For demonstration, use TLS in production
.build();
asyncStub = AuthServiceGrpc.newStub(channel);
}
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
public void callServiceWithKey(String method, String name, String apiKey) throws InterruptedException {
System.out.println("--- Calling " + method + " with API Key: " + apiKey + " ---");
final CountDownLatch latch = new CountDownLatch(1);
Metadata headers = new Metadata();
Metadata.Key<String> apiKeyHeader = Metadata.Key.of("api-key", Metadata.ASCII_STRING_MARSHALLER);
headers.put(apiKeyHeader, apiKey);
AuthServiceGrpc.AuthServiceStub authenticatedStub = MetadataUtils.attachHeaders(asyncStub, headers);
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
StreamObserver<HelloResponse> responseObserver = new StreamObserver<HelloResponse>() {
@Override
public void onNext(HelloResponse response) {
System.out.println("Response: " + response.getMessage());
}
@Override
public void onError(Throwable t) {
System.err.println("Error calling " + method + ": " + t.getMessage());
latch.countDown();
}
@Override
public void onCompleted() {
System.out.println("Call completed.");
latch.countDown();
}
};
if ("SayHello".equals(method)) {
authenticatedStub.sayHello(request, responseObserver);
} else if ("SayAdminHello".equals(method)) {
authenticatedStub.sayAdminHello(request, responseObserver);
} else {
System.err.println("Unknown method: " + method);
latch.countDown();
}
latch.await(1, TimeUnit.MINUTES);
}
public static void main(String[] args) throws Exception {
AuthClient client = new AuthClient("localhost", 50051);
try {
// These keys would be issued to different clients
String validApiKey = "my-secret-api-key-123";
String adminApiKey = "admin-secret-key-456";
String invalidApiKey = "wrong-key";
client.callServiceWithKey("SayHello", "Alice", validApiKey);
Thread.sleep(500);
client.callServiceWithKey("SayHello", "Bob", invalidApiKey);
Thread.sleep(500);
client.callServiceWithKey("SayAdminHello", "Charlie", validApiKey);
Thread.sleep(500);
client.callServiceWithKey("SayAdminHello", "AdminUser", adminApiKey);
Thread.sleep(500);
} finally {
client.shutdown();
}
}
}Validating API Key (Server)
This server example demonstrates how an interceptor extracts the API-Key from the request metadata and performs initial authentication. If valid, the key is attached to the Context.
Service methods then retrieve the key from the Context for fine-grained authorization checks. This is a common and robust pattern.
Try running this example (start this server, then the client from the previous scene):
// auth_service.proto (simplified for context in code)
// syntax = "proto3";
// option java_multiple_files = true;
// option java_package = "com.coddykit.grpc.auth";
// option java_outer_classname = "AuthServiceProto";
// package auth;
// service AuthService {
// rpc SayHello (HelloRequest) returns (HelloResponse);
// rpc SayAdminHello (HelloRequest) returns (HelloResponse);
// }
// message HelloRequest { string name = 1; }
// message HelloResponse { string message = 1; }
package com.coddykit.grpc.auth;
import io.grpc.Context;
import io.grpc.Metadata;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.ServerCall;
import io.grpc.ServerCallHandler;
import io.grpc.ServerInterceptor;
import io.grpc.Status;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.util.logging.Logger;
public class AuthServer {
private static final Logger logger = Logger.getLogger(AuthServer.class.getName());
private Server server;
private void start() throws IOException {
int port = 50051;
server = ServerBuilder.forPort(port)
.addService(new AuthServiceImpl())
.intercept(new AuthInterceptor()) // Add our authentication interceptor
.build()
.start();
logger.info("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.err.println("*** shutting down gRPC server since JVM is shutting down");
try {
AuthServer.this.stop();
} catch (InterruptedException e) {
e.printStackTrace(System.err);
}
System.err.println("*** server shut down");
}));
}
private void stop() throws InterruptedException {
if (server != null) {
server.shutdown().awaitTermination(30, java.util.concurrent.TimeUnit.SECONDS);
}
}
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
public static void main(String[] args) throws IOException, InterruptedException {
final AuthServer server = new AuthServer();
server.start();
server.blockUntilShutdown();
}
// Context key to store the authenticated API Key after interceptor processing
static final Context.Key<String> AUTH_API_KEY = Context.key("api-key");
// A simple, hardcoded valid API key for demonstration
private static final String VALID_API_KEY = "my-secret-api-key-123";
private static final String ADMIN_API_KEY = "admin-secret-key-456"; // For authorization example
static class AuthInterceptor implements ServerInterceptor {
static final Metadata.Key<String> API_KEY_METADATA_KEY =
Metadata.Key.of("api-key", Metadata.ASCII_STRING_MARSHALLER);
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
ServerCall<ReqT, RespT> call,
Metadata headers,
ServerCallHandler<ReqT, RespT> next) {
String apiKey = headers.get(API_KEY_METADATA_KEY);
// Basic Authentication check in the interceptor
if (apiKey == null || (!apiKey.equals(VALID_API_KEY) && !apiKey.equals(ADMIN_API_KEY))) {
logger.warning("AuthInterceptor: Authentication failed - Invalid or missing API key.");
call.close(Status.UNAUTHENTICATED.withDescription("Missing or invalid API key"), headers);
return new ServerCall.Listener<ReqT>() {}; // No-op listener
}
// If authenticated, attach the API key to the Context for later use by service methods
Context context = Context.current().withValue(AUTH_API_KEY, apiKey);
return Context.current().call(() -> next.startCall(call, headers));
}
}
static class AuthServiceImpl extends AuthServiceGrpc.AuthServiceImplBase {
@Override
public void sayHello(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
String apiKey = AUTH_API_KEY.get(); // Get API key from Context (set by interceptor)
logger.info("AuthServiceImpl: sayHello called with authenticated API Key: " + apiKey);
// No further authorization needed for SayHello, as authentication was done by interceptor
String message = "Hello " + request.getName() + " from authenticated service!";
HelloResponse response = HelloResponse.newBuilder().setMessage(message).build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
@Override
public void sayAdminHello(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
String apiKey = AUTH_API_KEY.get(); // Get API key from Context (set by interceptor)
// Authorization check (only admin key can access this specific method)
if (!apiKey.equals(ADMIN_API_KEY)) {
logger.warning("AuthServiceImpl: Authorization failed - API key " + apiKey + " is not authorized for admin access.");
responseObserver.onError(
Status.PERMISSION_DENIED
.withDescription("Access denied: Requires admin privileges")
.asRuntimeException());
return;
}
logger.info("AuthServiceImpl: sayAdminHello called with authorized API Key: " + apiKey);
String message = "Hello Admin " + request.getName() + " from secure service!";
HelloResponse response = HelloResponse.newBuilder().setMessage(message).build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}
}Check Your Understanding
Time for a quick check!
Recap: Auth & AuthZ
Great job! In this lesson, we explored:
- The difference between Authentication (who you are) and Authorization (what you can do).
- How clients send credentials via gRPC metadata.
- How servers can extract these credentials and apply both authentication and authorization logic, often with the help of interceptors.
Securing your gRPC services is a critical step towards building robust and reliable distributed applications!
Frequently asked questions
Is the “Authentication and Authorization” lesson free?
Yes — the full text of “Authentication and Authorization” is free to read here on the web, and the gRPC & High Performance APIs course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the gRPC & High Performance APIs course, upgrade to CoddyKit PRO.
What will I learn in “Authentication and Authorization”?
Explore strategies for authenticating clients and authorizing access to gRPC service methods. You practise gRPC & High Performance APIs with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start gRPC & High Performance APIs?
No prior experience is required. gRPC & High Performance APIs on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Authentication and Authorization” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this gRPC & High Performance APIs lesson?
Yes. Every gRPC & High Performance APIs lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- TLS/SSL for gRPC
- Authentication and Authorization
- Interceptors for Security
- Mutual TLS (mTLS) for Service-to-Service Auth