diff --git a/services/identity/src/database.rs b/services/identity/src/database.rs
--- a/services/identity/src/database.rs
+++ b/services/identity/src/database.rs
@@ -11,7 +11,6 @@
   AttributeExtractor, AttributeMap, DBItemAttributeError, DBItemError,
   TryFromAttribute,
 };
-use constant_time_eq::constant_time_eq;
 use std::collections::{HashMap, HashSet};
 use std::str::FromStr;
 use std::sync::Arc;
@@ -34,11 +33,8 @@
 use crate::client_service::{FlattenedDeviceKeyUpload, UserRegistrationInfo};
 use crate::config::CONFIG;
 use crate::constants::{
-  ACCESS_TOKEN_SORT_KEY, ACCESS_TOKEN_TABLE,
-  ACCESS_TOKEN_TABLE_AUTH_TYPE_ATTRIBUTE, ACCESS_TOKEN_TABLE_CREATED_ATTRIBUTE,
-  ACCESS_TOKEN_TABLE_PARTITION_KEY, ACCESS_TOKEN_TABLE_TOKEN_ATTRIBUTE,
-  ACCESS_TOKEN_TABLE_VALID_ATTRIBUTE, NONCE_TABLE,
-  NONCE_TABLE_CREATED_ATTRIBUTE, NONCE_TABLE_EXPIRATION_TIME_ATTRIBUTE,
+  NONCE_TABLE, NONCE_TABLE_CREATED_ATTRIBUTE,
+  NONCE_TABLE_EXPIRATION_TIME_ATTRIBUTE,
   NONCE_TABLE_EXPIRATION_TIME_UNIX_ATTRIBUTE, NONCE_TABLE_PARTITION_KEY,
   RESERVED_USERNAMES_TABLE, RESERVED_USERNAMES_TABLE_PARTITION_KEY,
   RESERVED_USERNAMES_TABLE_USER_ID_ATTRIBUTE, USERS_TABLE,
@@ -50,12 +46,13 @@
 };
 use crate::id::generate_uuid;
 use crate::nonce::NonceData;
-use crate::token::{AccessTokenData, AuthType};
+use crate::token::AuthType;
 pub use grpc_clients::identity::DeviceType;
 
 mod device_list;
 mod farcaster;
 mod one_time_keys;
+mod token;
 mod workflows;
 pub use device_list::{DeviceListRow, DeviceListUpdate, DeviceRow};
 
@@ -479,160 +476,6 @@
     }
   }
 
-  pub async fn get_access_token_data(
-    &self,
-    user_id: String,
-    signing_public_key: String,
-  ) -> Result<Option<AccessTokenData>, Error> {
-    let primary_key = create_composite_primary_key(
-      (
-        ACCESS_TOKEN_TABLE_PARTITION_KEY.to_string(),
-        user_id.clone(),
-      ),
-      (
-        ACCESS_TOKEN_SORT_KEY.to_string(),
-        signing_public_key.clone(),
-      ),
-    );
-    let get_item_result = self
-      .client
-      .get_item()
-      .table_name(ACCESS_TOKEN_TABLE)
-      .set_key(Some(primary_key))
-      .consistent_read(true)
-      .send()
-      .await;
-    match get_item_result {
-      Ok(GetItemOutput {
-        item: Some(mut item),
-        ..
-      }) => {
-        let created = DateTime::<Utc>::try_from_attr(
-          ACCESS_TOKEN_TABLE_CREATED_ATTRIBUTE,
-          item.remove(ACCESS_TOKEN_TABLE_CREATED_ATTRIBUTE),
-        )?;
-        let auth_type = parse_auth_type_attribute(
-          item.remove(ACCESS_TOKEN_TABLE_AUTH_TYPE_ATTRIBUTE),
-        )?;
-        let valid = parse_valid_attribute(
-          item.remove(ACCESS_TOKEN_TABLE_VALID_ATTRIBUTE),
-        )?;
-        let access_token = parse_token_attribute(
-          item.remove(ACCESS_TOKEN_TABLE_TOKEN_ATTRIBUTE),
-        )?;
-        Ok(Some(AccessTokenData {
-          user_id,
-          signing_public_key,
-          access_token,
-          created,
-          auth_type,
-          valid,
-        }))
-      }
-      Ok(_) => {
-        info!(
-          "No item found for user {} and signing public key {} in token table",
-          user_id, signing_public_key
-        );
-        Ok(None)
-      }
-      Err(e) => {
-        error!(
-          "DynamoDB client failed to get token for user {} with signing public key {}: {}",
-          user_id, signing_public_key, e
-        );
-        Err(Error::AwsSdk(e.into()))
-      }
-    }
-  }
-
-  pub async fn verify_access_token(
-    &self,
-    user_id: String,
-    signing_public_key: String,
-    access_token_to_verify: String,
-  ) -> Result<bool, Error> {
-    let is_valid = self
-      .get_access_token_data(user_id, signing_public_key)
-      .await?
-      .map(|access_token_data| {
-        constant_time_eq(
-          access_token_data.access_token.as_bytes(),
-          access_token_to_verify.as_bytes(),
-        ) && access_token_data.is_valid()
-      })
-      .unwrap_or(false);
-
-    Ok(is_valid)
-  }
-
-  pub async fn put_access_token_data(
-    &self,
-    access_token_data: AccessTokenData,
-  ) -> Result<PutItemOutput, Error> {
-    let item = HashMap::from([
-      (
-        ACCESS_TOKEN_TABLE_PARTITION_KEY.to_string(),
-        AttributeValue::S(access_token_data.user_id),
-      ),
-      (
-        ACCESS_TOKEN_SORT_KEY.to_string(),
-        AttributeValue::S(access_token_data.signing_public_key),
-      ),
-      (
-        ACCESS_TOKEN_TABLE_TOKEN_ATTRIBUTE.to_string(),
-        AttributeValue::S(access_token_data.access_token),
-      ),
-      (
-        ACCESS_TOKEN_TABLE_CREATED_ATTRIBUTE.to_string(),
-        AttributeValue::S(access_token_data.created.to_rfc3339()),
-      ),
-      (
-        ACCESS_TOKEN_TABLE_AUTH_TYPE_ATTRIBUTE.to_string(),
-        AttributeValue::S(match access_token_data.auth_type {
-          AuthType::Password => "password".to_string(),
-          AuthType::Wallet => "wallet".to_string(),
-        }),
-      ),
-      (
-        ACCESS_TOKEN_TABLE_VALID_ATTRIBUTE.to_string(),
-        AttributeValue::Bool(access_token_data.valid),
-      ),
-    ]);
-    self
-      .client
-      .put_item()
-      .table_name(ACCESS_TOKEN_TABLE)
-      .set_item(Some(item))
-      .send()
-      .await
-      .map_err(|e| Error::AwsSdk(e.into()))
-  }
-
-  pub async fn delete_access_token_data(
-    &self,
-    user_id: String,
-    device_id_key: String,
-  ) -> Result<(), Error> {
-    self
-      .client
-      .delete_item()
-      .table_name(ACCESS_TOKEN_TABLE)
-      .key(
-        ACCESS_TOKEN_TABLE_PARTITION_KEY.to_string(),
-        AttributeValue::S(user_id),
-      )
-      .key(
-        ACCESS_TOKEN_SORT_KEY.to_string(),
-        AttributeValue::S(device_id_key),
-      )
-      .send()
-      .await
-      .map_err(|e| Error::AwsSdk(e.into()))?;
-
-    Ok(())
-  }
-
   pub async fn wallet_address_taken(
     &self,
     wallet_address: String,
@@ -1157,64 +1000,6 @@
   primary_key
 }
 
-fn parse_auth_type_attribute(
-  attribute: Option<AttributeValue>,
-) -> Result<AuthType, DBItemError> {
-  if let Some(AttributeValue::S(auth_type)) = &attribute {
-    match auth_type.as_str() {
-      "password" => Ok(AuthType::Password),
-      "wallet" => Ok(AuthType::Wallet),
-      _ => Err(DBItemError::new(
-        ACCESS_TOKEN_TABLE_AUTH_TYPE_ATTRIBUTE.to_string(),
-        attribute.into(),
-        DBItemAttributeError::IncorrectType,
-      )),
-    }
-  } else {
-    Err(DBItemError::new(
-      ACCESS_TOKEN_TABLE_AUTH_TYPE_ATTRIBUTE.to_string(),
-      attribute.into(),
-      DBItemAttributeError::Missing,
-    ))
-  }
-}
-
-fn parse_valid_attribute(
-  attribute: Option<AttributeValue>,
-) -> Result<bool, DBItemError> {
-  match attribute {
-    Some(AttributeValue::Bool(valid)) => Ok(valid),
-    Some(_) => Err(DBItemError::new(
-      ACCESS_TOKEN_TABLE_VALID_ATTRIBUTE.to_string(),
-      attribute.into(),
-      DBItemAttributeError::IncorrectType,
-    )),
-    None => Err(DBItemError::new(
-      ACCESS_TOKEN_TABLE_VALID_ATTRIBUTE.to_string(),
-      attribute.into(),
-      DBItemAttributeError::Missing,
-    )),
-  }
-}
-
-fn parse_token_attribute(
-  attribute: Option<AttributeValue>,
-) -> Result<String, DBItemError> {
-  match attribute {
-    Some(AttributeValue::S(token)) => Ok(token),
-    Some(_) => Err(DBItemError::new(
-      ACCESS_TOKEN_TABLE_TOKEN_ATTRIBUTE.to_string(),
-      attribute.into(),
-      DBItemAttributeError::IncorrectType,
-    )),
-    None => Err(DBItemError::new(
-      ACCESS_TOKEN_TABLE_TOKEN_ATTRIBUTE.to_string(),
-      attribute.into(),
-      DBItemAttributeError::Missing,
-    )),
-  }
-}
-
 fn parse_registration_data_attribute(
   attribute: Option<AttributeValue>,
 ) -> Result<Vec<u8>, DBItemError> {
diff --git a/services/identity/src/database/token.rs b/services/identity/src/database/token.rs
new file mode 100644
--- /dev/null
+++ b/services/identity/src/database/token.rs
@@ -0,0 +1,239 @@
+use std::collections::HashMap;
+
+use chrono::{DateTime, Utc};
+use comm_lib::{
+  aws::ddb::{
+    operation::{get_item::GetItemOutput, put_item::PutItemOutput},
+    types::AttributeValue,
+  },
+  database::{DBItemAttributeError, DBItemError, TryFromAttribute},
+};
+use constant_time_eq::constant_time_eq;
+use tracing::{error, info};
+
+use crate::{
+  constants::{
+    ACCESS_TOKEN_SORT_KEY, ACCESS_TOKEN_TABLE,
+    ACCESS_TOKEN_TABLE_AUTH_TYPE_ATTRIBUTE,
+    ACCESS_TOKEN_TABLE_CREATED_ATTRIBUTE, ACCESS_TOKEN_TABLE_PARTITION_KEY,
+    ACCESS_TOKEN_TABLE_TOKEN_ATTRIBUTE, ACCESS_TOKEN_TABLE_VALID_ATTRIBUTE,
+  },
+  error::Error,
+  token::{AccessTokenData, AuthType},
+};
+
+use super::{create_composite_primary_key, DatabaseClient};
+
+impl DatabaseClient {
+  pub async fn get_access_token_data(
+    &self,
+    user_id: String,
+    signing_public_key: String,
+  ) -> Result<Option<AccessTokenData>, Error> {
+    let primary_key = create_composite_primary_key(
+      (
+        ACCESS_TOKEN_TABLE_PARTITION_KEY.to_string(),
+        user_id.clone(),
+      ),
+      (
+        ACCESS_TOKEN_SORT_KEY.to_string(),
+        signing_public_key.clone(),
+      ),
+    );
+    let get_item_result = self
+      .client
+      .get_item()
+      .table_name(ACCESS_TOKEN_TABLE)
+      .set_key(Some(primary_key))
+      .consistent_read(true)
+      .send()
+      .await;
+    match get_item_result {
+      Ok(GetItemOutput {
+        item: Some(mut item),
+        ..
+      }) => {
+        let created = DateTime::<Utc>::try_from_attr(
+          ACCESS_TOKEN_TABLE_CREATED_ATTRIBUTE,
+          item.remove(ACCESS_TOKEN_TABLE_CREATED_ATTRIBUTE),
+        )?;
+        let auth_type = parse_auth_type_attribute(
+          item.remove(ACCESS_TOKEN_TABLE_AUTH_TYPE_ATTRIBUTE),
+        )?;
+        let valid = parse_valid_attribute(
+          item.remove(ACCESS_TOKEN_TABLE_VALID_ATTRIBUTE),
+        )?;
+        let access_token = parse_token_attribute(
+          item.remove(ACCESS_TOKEN_TABLE_TOKEN_ATTRIBUTE),
+        )?;
+        Ok(Some(AccessTokenData {
+          user_id,
+          signing_public_key,
+          access_token,
+          created,
+          auth_type,
+          valid,
+        }))
+      }
+      Ok(_) => {
+        info!(
+          "No item found for user {} and signing public key {} in token table",
+          user_id, signing_public_key
+        );
+        Ok(None)
+      }
+      Err(e) => {
+        error!(
+          "DynamoDB client failed to get token for user {} with signing public key {}: {}",
+          user_id, signing_public_key, e
+        );
+        Err(Error::AwsSdk(e.into()))
+      }
+    }
+  }
+
+  pub async fn verify_access_token(
+    &self,
+    user_id: String,
+    signing_public_key: String,
+    access_token_to_verify: String,
+  ) -> Result<bool, Error> {
+    let is_valid = self
+      .get_access_token_data(user_id, signing_public_key)
+      .await?
+      .map(|access_token_data| {
+        constant_time_eq(
+          access_token_data.access_token.as_bytes(),
+          access_token_to_verify.as_bytes(),
+        ) && access_token_data.is_valid()
+      })
+      .unwrap_or(false);
+
+    Ok(is_valid)
+  }
+
+  pub async fn put_access_token_data(
+    &self,
+    access_token_data: AccessTokenData,
+  ) -> Result<PutItemOutput, Error> {
+    let item = HashMap::from([
+      (
+        ACCESS_TOKEN_TABLE_PARTITION_KEY.to_string(),
+        AttributeValue::S(access_token_data.user_id),
+      ),
+      (
+        ACCESS_TOKEN_SORT_KEY.to_string(),
+        AttributeValue::S(access_token_data.signing_public_key),
+      ),
+      (
+        ACCESS_TOKEN_TABLE_TOKEN_ATTRIBUTE.to_string(),
+        AttributeValue::S(access_token_data.access_token),
+      ),
+      (
+        ACCESS_TOKEN_TABLE_CREATED_ATTRIBUTE.to_string(),
+        AttributeValue::S(access_token_data.created.to_rfc3339()),
+      ),
+      (
+        ACCESS_TOKEN_TABLE_AUTH_TYPE_ATTRIBUTE.to_string(),
+        AttributeValue::S(match access_token_data.auth_type {
+          AuthType::Password => "password".to_string(),
+          AuthType::Wallet => "wallet".to_string(),
+        }),
+      ),
+      (
+        ACCESS_TOKEN_TABLE_VALID_ATTRIBUTE.to_string(),
+        AttributeValue::Bool(access_token_data.valid),
+      ),
+    ]);
+    self
+      .client
+      .put_item()
+      .table_name(ACCESS_TOKEN_TABLE)
+      .set_item(Some(item))
+      .send()
+      .await
+      .map_err(|e| Error::AwsSdk(e.into()))
+  }
+
+  pub async fn delete_access_token_data(
+    &self,
+    user_id: String,
+    device_id_key: String,
+  ) -> Result<(), Error> {
+    self
+      .client
+      .delete_item()
+      .table_name(ACCESS_TOKEN_TABLE)
+      .key(
+        ACCESS_TOKEN_TABLE_PARTITION_KEY.to_string(),
+        AttributeValue::S(user_id),
+      )
+      .key(
+        ACCESS_TOKEN_SORT_KEY.to_string(),
+        AttributeValue::S(device_id_key),
+      )
+      .send()
+      .await
+      .map_err(|e| Error::AwsSdk(e.into()))?;
+
+    Ok(())
+  }
+}
+
+fn parse_auth_type_attribute(
+  attribute: Option<AttributeValue>,
+) -> Result<AuthType, DBItemError> {
+  if let Some(AttributeValue::S(auth_type)) = &attribute {
+    match auth_type.as_str() {
+      "password" => Ok(AuthType::Password),
+      "wallet" => Ok(AuthType::Wallet),
+      _ => Err(DBItemError::new(
+        ACCESS_TOKEN_TABLE_AUTH_TYPE_ATTRIBUTE.to_string(),
+        attribute.into(),
+        DBItemAttributeError::IncorrectType,
+      )),
+    }
+  } else {
+    Err(DBItemError::new(
+      ACCESS_TOKEN_TABLE_AUTH_TYPE_ATTRIBUTE.to_string(),
+      attribute.into(),
+      DBItemAttributeError::Missing,
+    ))
+  }
+}
+
+fn parse_valid_attribute(
+  attribute: Option<AttributeValue>,
+) -> Result<bool, DBItemError> {
+  match attribute {
+    Some(AttributeValue::Bool(valid)) => Ok(valid),
+    Some(_) => Err(DBItemError::new(
+      ACCESS_TOKEN_TABLE_VALID_ATTRIBUTE.to_string(),
+      attribute.into(),
+      DBItemAttributeError::IncorrectType,
+    )),
+    None => Err(DBItemError::new(
+      ACCESS_TOKEN_TABLE_VALID_ATTRIBUTE.to_string(),
+      attribute.into(),
+      DBItemAttributeError::Missing,
+    )),
+  }
+}
+
+fn parse_token_attribute(
+  attribute: Option<AttributeValue>,
+) -> Result<String, DBItemError> {
+  match attribute {
+    Some(AttributeValue::S(token)) => Ok(token),
+    Some(_) => Err(DBItemError::new(
+      ACCESS_TOKEN_TABLE_TOKEN_ATTRIBUTE.to_string(),
+      attribute.into(),
+      DBItemAttributeError::IncorrectType,
+    )),
+    None => Err(DBItemError::new(
+      ACCESS_TOKEN_TABLE_TOKEN_ATTRIBUTE.to_string(),
+      attribute.into(),
+      DBItemAttributeError::Missing,
+    )),
+  }
+}