diff --git a/services/identity/Dockerfile b/services/identity/Dockerfile
--- a/services/identity/Dockerfile
+++ b/services/identity/Dockerfile
@@ -13,10 +13,16 @@
 
 RUN mkdir -p /home/comm/app/identity
 WORKDIR /home/comm/app/identity
+RUN cargo init --bin
+
+COPY services/identity/Cargo.toml services/identity/Cargo.lock ./
+COPY shared/ ../../shared/
+
+# Cache build dependencies in a new layer
+RUN cargo build --release
+RUN rm src/*.rs
 
 COPY services/identity .
-COPY shared/protos/identity_client.proto ../../shared/protos/
-COPY shared/comm-opaque2 ../../shared/comm-opaque2
 
 RUN cargo install --locked --path .
 
diff --git a/services/identity/build.rs b/services/identity/build.rs
--- a/services/identity/build.rs
+++ b/services/identity/build.rs
@@ -3,7 +3,10 @@
     .build_server(true)
     .build_client(false)
     .compile(
-      &["../../shared/protos/identity_client.proto"],
+      &[
+        "../../shared/protos/identity_client.proto",
+        "../../shared/protos/identity_authenticated.proto",
+      ],
       &["../../shared/protos/"],
     )?;
   Ok(())
diff --git a/services/identity/src/grpc_services/authenticated.rs b/services/identity/src/grpc_services/authenticated.rs
new file mode 100644
--- /dev/null
+++ b/services/identity/src/grpc_services/authenticated.rs
@@ -0,0 +1,79 @@
+use crate::{client_service::handle_db_error, database::DatabaseClient};
+use tonic::{Request, Response, Status};
+
+// This must be named client, because generated code from the authenticated
+// protobuf file references message structs from the client protobuf file
+// with the client:: namespace
+pub mod client {
+  tonic::include_proto!("identity.client");
+}
+pub mod auth_proto {
+  tonic::include_proto!("identity.authenticated");
+}
+use auth_proto::{
+  identity_client_service_server::IdentityClientService,
+  RefreshUserPreKeysRequest,
+};
+use client::Empty;
+use tracing::debug;
+
+#[derive(derive_more::Constructor)]
+pub struct AuthenticatedService {
+  db_client: DatabaseClient,
+}
+
+fn get_value<T>(req: &Request<T>, key: &str) -> Option<String> {
+  let raw_value = req.metadata().get(key)?;
+  raw_value.to_str().ok().map(|s| s.to_string())
+}
+
+fn get_auth_info(req: &Request<()>) -> Option<(String, String, String)> {
+  debug!("Retrieving auth info for request: {:?}", req);
+
+  let user_id = get_value(req, "user_id")?;
+  let device_id = get_value(req, "device_id")?;
+  let access_token = get_value(req, "access_token")?;
+
+  Some((user_id, device_id, access_token))
+}
+
+pub fn auth_intercept(
+  req: Request<()>,
+  db_client: &DatabaseClient,
+) -> Result<Request<()>, Status> {
+  println!("Intercepting request: {:?}", req);
+
+  let (user_id, device_id, access_token) = get_auth_info(&req)
+    .ok_or(Status::unauthenticated("Missing credentials"))?;
+
+  let handle = tokio::runtime::Handle::current();
+  let new_db_client = db_client.clone();
+
+  // This function cannot be `async`, yet must call the async db call
+  // Force tokio to resolve future in current thread without an explicit .await
+  let valid_token = tokio::task::block_in_place(move || {
+    handle
+      .block_on(new_db_client.verify_access_token(
+        user_id,
+        device_id,
+        access_token,
+      ))
+      .map_err(handle_db_error)
+  })?;
+
+  if !valid_token {
+    return Err(Status::aborted("Bad Credentials"));
+  }
+
+  Ok(req)
+}
+
+#[tonic::async_trait]
+impl IdentityClientService for AuthenticatedService {
+  async fn refresh_user_pre_keys(
+    &self,
+    _request: Request<RefreshUserPreKeysRequest>,
+  ) -> Result<Response<Empty>, Status> {
+    unimplemented!();
+  }
+}
diff --git a/services/identity/src/grpc_services/mod.rs b/services/identity/src/grpc_services/mod.rs
new file mode 100644
--- /dev/null
+++ b/services/identity/src/grpc_services/mod.rs
@@ -0,0 +1 @@
+pub mod authenticated;
diff --git a/services/identity/src/main.rs b/services/identity/src/main.rs
--- a/services/identity/src/main.rs
+++ b/services/identity/src/main.rs
@@ -9,6 +9,7 @@
 mod config;
 pub mod constants;
 mod database;
+mod grpc_services;
 mod id;
 mod keygen;
 mod nonce;
@@ -23,6 +24,8 @@
 use tracing_subscriber::EnvFilter;
 
 use client_service::{ClientService, IdentityClientServiceServer};
+use grpc_services::authenticated::auth_proto::identity_client_service_server::IdentityClientServiceServer as AuthServer;
+use grpc_services::authenticated::AuthenticatedService;
 
 #[derive(Parser)]
 #[clap(author, version, about, long_about = None)]
@@ -70,12 +73,19 @@
         .time_to_live(Duration::from_secs(10))
         .build();
       let client_service = IdentityClientServiceServer::new(
-        ClientService::new(database_client, workflow_cache),
+        ClientService::new(database_client.clone(), workflow_cache),
       );
+      let raw_auth_service = AuthenticatedService::new(database_client.clone());
+      let auth_service =
+        AuthServer::with_interceptor(raw_auth_service, move |mut req| {
+          grpc_services::authenticated::auth_intercept(req, &database_client)
+        });
+
       info!("Listening to gRPC traffic on {}", addr);
       Server::builder()
         .accept_http1(true)
         .add_service(tonic_web::enable(client_service))
+        .add_service(auth_service)
         .serve(addr)
         .await?;
     }
diff --git a/shared/protos/identity_authenticated.proto b/shared/protos/identity_authenticated.proto
new file mode 100644
--- /dev/null
+++ b/shared/protos/identity_authenticated.proto
@@ -0,0 +1,27 @@
+syntax = "proto3";
+
+import "identity_client.proto";
+
+package identity.authenticated;
+
+// RPCs from a client (iOS, Android, or web) to identity service
+//
+// This service will assert authenticity of a device by verifying the access
+// token through an interceptor, thus avoiding the need to explicitly pass
+// the credentials on every request
+service IdentityClientService {
+
+  // Rotate a devices preKey and preKey signature
+  // Rotated for deniability of older messages
+  rpc RefreshUserPreKeys(RefreshUserPreKeysRequest)
+    returns (identity.client.Empty) {}
+}
+
+// Helper types
+
+// RefreshUserPreKeys
+
+message RefreshUserPreKeysRequest {
+  identity.client.PreKey newContentPreKeys = 1;
+  identity.client.PreKey newNotifPreKeys = 2;
+}