diff --git a/services/backup/Cargo.lock b/services/backup/Cargo.lock
--- a/services/backup/Cargo.lock
+++ b/services/backup/Cargo.lock
@@ -45,7 +45,7 @@
  "actix-service",
  "actix-utils",
  "ahash 0.8.3",
- "base64",
+ "base64 0.21.2",
  "bitflags 1.3.2",
  "brotli",
  "bytes",
@@ -236,6 +236,21 @@
  "syn 1.0.109",
 ]
 
+[[package]]
+name = "actix-web-httpauth"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dda62cf04bc3a9ad2ea8f314f721951cfdb4cdacec4e984d20e77c7bb170991"
+dependencies = [
+ "actix-utils",
+ "actix-web",
+ "base64 0.13.1",
+ "futures-core",
+ "futures-util",
+ "log",
+ "pin-project-lite",
+]
+
 [[package]]
 name = "addr2line"
 version = "0.21.0"
@@ -754,6 +769,12 @@
  "uuid",
 ]
 
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
 [[package]]
 name = "base64"
 version = "0.21.2"
@@ -928,10 +949,12 @@
  "actix-cors",
  "actix-multipart",
  "actix-web",
+ "actix-web-httpauth",
  "anyhow",
  "aws-config",
  "aws-sdk-dynamodb",
  "aws-types",
+ "base64 0.21.2",
  "chrono",
  "derive_more",
  "futures-core",
@@ -2051,7 +2074,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1"
 dependencies = [
- "base64",
+ "base64 0.21.2",
  "bytes",
  "encoding_rs",
  "futures-core",
@@ -2158,7 +2181,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2"
 dependencies = [
- "base64",
+ "base64 0.21.2",
 ]
 
 [[package]]
diff --git a/services/blob/Cargo.lock b/services/blob/Cargo.lock
--- a/services/blob/Cargo.lock
+++ b/services/blob/Cargo.lock
@@ -236,6 +236,21 @@
  "syn 1.0.103",
 ]
 
+[[package]]
+name = "actix-web-httpauth"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dda62cf04bc3a9ad2ea8f314f721951cfdb4cdacec4e984d20e77c7bb170991"
+dependencies = [
+ "actix-utils",
+ "actix-web",
+ "base64 0.13.1",
+ "futures-core",
+ "futures-util",
+ "log",
+ "pin-project-lite",
+]
+
 [[package]]
 name = "adler"
 version = "1.0.2"
@@ -978,15 +993,19 @@
  "actix-cors",
  "actix-multipart",
  "actix-web",
+ "actix-web-httpauth",
  "anyhow",
  "aws-config",
  "aws-sdk-dynamodb",
  "aws-types",
+ "base64 0.21.0",
  "chrono",
  "derive_more",
  "futures-core",
  "futures-util",
  "http",
+ "serde",
+ "serde_json",
  "tokio-stream",
  "tracing",
 ]
diff --git a/services/comm-services-lib/Cargo.lock b/services/comm-services-lib/Cargo.lock
--- a/services/comm-services-lib/Cargo.lock
+++ b/services/comm-services-lib/Cargo.lock
@@ -45,7 +45,7 @@
  "actix-service",
  "actix-utils",
  "ahash 0.8.3",
- "base64",
+ "base64 0.21.0",
  "bitflags",
  "brotli",
  "bytes",
@@ -236,6 +236,21 @@
  "syn 1.0.109",
 ]
 
+[[package]]
+name = "actix-web-httpauth"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dda62cf04bc3a9ad2ea8f314f721951cfdb4cdacec4e984d20e77c7bb170991"
+dependencies = [
+ "actix-utils",
+ "actix-web",
+ "base64 0.13.1",
+ "futures-core",
+ "futures-util",
+ "log",
+ "pin-project-lite",
+]
+
 [[package]]
 name = "addr2line"
 version = "0.21.0"
@@ -652,6 +667,12 @@
  "rustc-demangle",
 ]
 
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
 [[package]]
 name = "base64"
 version = "0.21.0"
@@ -782,10 +803,12 @@
  "actix-cors",
  "actix-multipart",
  "actix-web",
+ "actix-web-httpauth",
  "anyhow",
  "aws-config",
  "aws-sdk-dynamodb",
  "aws-types",
+ "base64 0.21.0",
  "chrono",
  "derive_more",
  "futures-core",
@@ -1758,7 +1781,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55"
 dependencies = [
- "base64",
+ "base64 0.21.0",
  "bytes",
  "encoding_rs",
  "futures-core",
@@ -1866,7 +1889,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b"
 dependencies = [
- "base64",
+ "base64 0.21.0",
 ]
 
 [[package]]
diff --git a/services/comm-services-lib/Cargo.toml b/services/comm-services-lib/Cargo.toml
--- a/services/comm-services-lib/Cargo.toml
+++ b/services/comm-services-lib/Cargo.toml
@@ -9,8 +9,6 @@
   "dep:reqwest",
   "dep:futures-core",
   "dep:futures-util",
-  "dep:serde",
-  "dep:serde_json",
   "dep:tokio",
 ]
 http = [
@@ -21,12 +19,16 @@
   "dep:futures-util",
   "dep:http",
   "dep:tokio-stream",
+  "dep:actix-web-httpauth",
 ]
 
 [dependencies]
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
 aws-config = "0.55"
 aws-sdk-dynamodb = "0.27"
 aws-types = "0.55"
+base64 = "0.21"
 chrono = "0.4"
 derive_more = "0.99"
 tracing = "0.1"
@@ -39,12 +41,11 @@
   "multipart",
   "stream",
 ], optional = true }
-serde = { version = "1.0", features = ["derive"], optional = true }
-serde_json = { version = "1.0", optional = true }
 tokio = { version = "1.32", optional = true }
 # http dependencies
 actix-cors = { version = "0.6", optional = true }
 actix-web = { version = "4.3", optional = true }
 http = { version = "0.2.9", optional = true }
+actix-web-httpauth = { version = "0.8.0", optional = true }
 actix-multipart = { version = "0.6", optional = true }
 tokio-stream = { version = "0.1.14", optional = true }
diff --git a/services/comm-services-lib/src/auth.rs b/services/comm-services-lib/src/auth.rs
new file mode 100644
--- /dev/null
+++ b/services/comm-services-lib/src/auth.rs
@@ -0,0 +1,79 @@
+use base64::{prelude::BASE64_STANDARD, Engine};
+use derive_more::{Display, Error, From};
+use serde::{Deserialize, Serialize};
+use std::{str::FromStr, string::FromUtf8Error};
+
+/// This implements [`actix_web::FromRequest`], so it can be used to extract user
+/// identity information from HTTP requests.
+/// # Example
+/// ```ignore
+/// pub async fn request_handler(
+///  user: UserIdentity,
+/// ) -> Result<HttpResponse> {
+///   Ok(HttpResponse::Ok().finish())
+/// }
+/// ```
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+pub struct UserIdentity {
+  #[serde(rename = "userID")]
+  pub user_id: String,
+  #[serde(rename = "accessToken")]
+  pub access_token: String,
+  #[serde(rename = "deviceID")]
+  pub device_id: String,
+}
+
+impl UserIdentity {
+  /// Gets the access token value, usable in bearer authorization
+  ///
+  /// # Example
+  /// ```ignore
+  /// reqwest::get("url").beaerer_auth(user.as_authorization_token()?).send().await?;
+  /// ```
+  pub fn as_authorization_token(&self) -> Result<String, serde_json::Error> {
+    let json = serde_json::to_string(self)?;
+    let base64_str = BASE64_STANDARD.encode(json);
+    Ok(base64_str)
+  }
+}
+
+#[derive(Debug, Display, Error, From)]
+pub enum UserIdentityParseError {
+  Base64DecodeError(base64::DecodeError),
+  Utf8DecodeError(FromUtf8Error),
+  JsonParseError(serde_json::Error),
+}
+
+/// Parsing of [UserIdentity] from bearer token
+impl FromStr for UserIdentity {
+  type Err = UserIdentityParseError;
+
+  fn from_str(s: &str) -> Result<Self, Self::Err> {
+    let bytes = BASE64_STANDARD.decode(s)?;
+    let text = String::from_utf8(bytes)?;
+    let user = serde_json::from_str(&text)?;
+    Ok(user)
+  }
+}
+
+#[cfg(test)]
+mod tests {
+  use super::*;
+
+  #[test]
+  fn test_user_identity_parsing() {
+    let identity = UserIdentity {
+      user_id: "user".to_string(),
+      access_token: "token".to_string(),
+      device_id: "device".to_string(),
+    };
+    let json =
+      r#"{"userID": "user", "accessToken": "token", "deviceID": "device"}"#;
+    let encoded = BASE64_STANDARD.encode(json);
+
+    let parsed_identity = encoded.parse::<UserIdentity>();
+    assert!(parsed_identity.is_ok(), "Parse error: {parsed_identity:?}");
+
+    assert_eq!(parsed_identity.unwrap(), identity);
+  }
+}
diff --git a/services/comm-services-lib/src/http.rs b/services/comm-services-lib/src/http.rs
--- a/services/comm-services-lib/src/http.rs
+++ b/services/comm-services-lib/src/http.rs
@@ -1,3 +1,4 @@
+pub mod auth;
 pub mod multipart;
 
 use crate::tools::BoxedError;
diff --git a/services/comm-services-lib/src/http/auth.rs b/services/comm-services-lib/src/http/auth.rs
new file mode 100644
--- /dev/null
+++ b/services/comm-services-lib/src/http/auth.rs
@@ -0,0 +1,108 @@
+use actix_web::{
+  body::{EitherBody, MessageBody},
+  dev::{Service, ServiceRequest, ServiceResponse, Transform},
+  FromRequest, HttpMessage,
+};
+use actix_web_httpauth::{
+  extractors::{bearer::BearerAuth, AuthenticationError},
+  headers::www_authenticate::bearer::Bearer,
+  middleware::HttpAuthentication,
+};
+use std::{
+  future::{ready, Ready},
+  str::FromStr,
+};
+use tracing::debug;
+
+use crate::auth::UserIdentity;
+
+impl FromRequest for UserIdentity {
+  type Error = actix_web::Error;
+  type Future = Ready<Result<Self, Self::Error>>;
+
+  fn from_request(
+    req: &actix_web::HttpRequest,
+    _: &mut actix_web::dev::Payload,
+  ) -> Self::Future {
+    if let Some(user) = req.extensions().get::<UserIdentity>() {
+      return ready(Ok(user.clone()));
+    }
+
+    let f = || {
+      let bearer = BearerAuth::extract(req).into_inner()?;
+      let user = match UserIdentity::from_str(bearer.token()) {
+        Ok(user) => user,
+        Err(err) => {
+          debug!("HTTP authorization error: {err}");
+          return Err(AuthenticationError::new(Bearer::default()).into());
+        }
+      };
+
+      Ok(user)
+    };
+
+    ready(f())
+  }
+}
+
+pub async fn validation_function(
+  req: ServiceRequest,
+  bearer: BearerAuth,
+) -> Result<ServiceRequest, (actix_web::Error, ServiceRequest)> {
+  let user = match UserIdentity::from_str(bearer.token()) {
+    Ok(user) => user,
+    Err(err) => {
+      debug!("HTTP authorization error: {err}");
+      return Err((AuthenticationError::new(Bearer::default()).into(), req));
+    }
+  };
+
+  // TODO: call identity service, for now just allow every request
+  req.extensions_mut().insert(user);
+
+  Ok(req)
+}
+
+/// Use this to add Authentication Middleware. It's going to parse Authorization
+/// header and call the identity service to check if the provided credentials
+/// are correct. If not it's going to reject the request.
+///
+/// # Example
+/// ```ignore
+/// let auth_middleware = get_comm_authentication_middleware();
+/// App::new().wrap(auth_middleware);
+/// ```
+/// If you don't want all of the routes to require authentication you can wrap
+/// individual resources or scopes:
+/// ```ignore
+/// App::new().service(
+///  web::resource("/endpoint").route(web::get().to(handler)).wrap(auth_middleware),
+/// )
+/// ```
+// This type is very complicated, but unfortunately typing this directly
+// requires https://github.com/rust-lang/rust/issues/99697 to be merged.
+// The issue is that we can't specify the second generic argument of
+// HttpAuthentication<T, F>, because it look something like this:
+// ```
+// impl Fn(ServiceRequest, BearerAuth) -> impl Future<
+//    Output = Result<ServiceRequest, (actix_web::Error, ServiceRequest)>,
+//  >
+// ``
+// which isn't valid (until the linked issue is merged).
+pub fn get_comm_authentication_middleware<B, S>() -> impl Transform<
+  S,
+  ServiceRequest,
+  Response = ServiceResponse<EitherBody<B>>,
+  Error = actix_web::Error,
+  InitError = (),
+> + 'static
+where
+  B: MessageBody + 'static,
+  S: Service<
+      ServiceRequest,
+      Response = ServiceResponse<B>,
+      Error = actix_web::Error,
+    > + 'static,
+{
+  HttpAuthentication::bearer(validation_function)
+}
diff --git a/services/comm-services-lib/src/lib.rs b/services/comm-services-lib/src/lib.rs
--- a/services/comm-services-lib/src/lib.rs
+++ b/services/comm-services-lib/src/lib.rs
@@ -1,3 +1,4 @@
+pub mod auth;
 pub mod blob;
 pub mod constants;
 pub mod database;
diff --git a/services/feature-flags/Cargo.lock b/services/feature-flags/Cargo.lock
--- a/services/feature-flags/Cargo.lock
+++ b/services/feature-flags/Cargo.lock
@@ -731,8 +731,11 @@
  "aws-config",
  "aws-sdk-dynamodb",
  "aws-types",
+ "base64",
  "chrono",
  "derive_more",
+ "serde",
+ "serde_json",
  "tracing",
 ]