15 — Identity, authorization, and TLS

Goal

Authenticate the caller, authorize each operation, and encrypt deployed network traffic.

Step 1: Separate identity from authorization

Use an identity provider (or a clearly isolated classroom identity service) to issue short-lived access tokens. A token proves who the caller is; every task operation must still check that identity's membership in the task's project/team. Never treat a client-supplied owner_id or team_id as authorization.

Authentication answers “who is calling?” Authorization answers “what may that caller do?” gRPC metadata is a transport for credentials, not proof that they are valid. The server must verify the credential and derive the principal itself. It then checks membership for each requested resource; a valid user can still be forbidden from a different team's data.

Step 2: Send a bearer token from Dart

Once the user is signed in, attach the token as call metadata:

final callOptions = CallOptions(metadata: {
  'authorization': 'Bearer $accessToken',
});
final task = await client.getTask(request, options: callOptions);

Refresh credentials through the identity provider when expired. Do not print tokens in logs or store them in plain preferences; use the platform's secure storage guidance.

Step 3: Authenticate in Go

Add a unary server interceptor and a stream server interceptor. Each interceptor should:

  1. Read the authorization metadata value.
  2. Require the Bearer prefix and reject missing/malformed credentials with Unauthenticated.
  3. Validate signature, issuer, audience, and expiry using the identity provider's supported library or verification endpoint.
  4. Put a small principal value (such as user ID) into the request context.
  5. Call the next handler only after validation.

Do not write a home-grown JWT verifier or accept a decoded token without signature verification. Add a helper to retrieve the principal from context. Application methods then check project membership using the repository before reading or changing data. Apply the same checks to WatchTasks before registering its stream.

Step 4: Configure TLS

For deployed Go servers, load the certificate and key through credentials.NewServerTLSFromFile and pass the returned credentials to grpc.NewServer(grpc.Creds(...)). For Flutter production clients, use secure channel credentials and the platform's trusted certificate chain. Do not ship an insecure channel in a release build. For local development, use a locally trusted certificate or a separate debug-only insecure configuration.

TLS encrypts traffic and lets the client verify it reached the intended server. It protects the token and task data while moving across a network. TLS does not replace authentication or authorization: it secures the connection, while credentials identify the caller and application rules determine access.

Step 5: Verify access boundaries

Create test users A and B in different teams. Confirm:

  • Missing token returns Unauthenticated.
  • Invalid or expired token returns Unauthenticated.
  • User A can read a task in team A.
  • User A cannot read, update, delete, or watch tasks in team B (PermissionDenied or consistently masked NotFound).

Test both unary and streaming interceptors. A long-lived stream should also have a maximum lifetime or terminate when credentials expire so the client can refresh and reconnect.

Checkpoint

Document where tokens come from, how Go validates them, and how team membership is enforced. Search logs and crash output to ensure no credentials are present.

Practice

Write a threat model for mobile token storage, lost phones, stream authorization, secret rotation, and accidental sensitive data in logs.

results matching ""

    No results matching ""