diff --git a/services/identity/src/constants.rs b/services/identity/src/constants.rs
--- a/services/identity/src/constants.rs
+++ b/services/identity/src/constants.rs
@@ -58,13 +58,15 @@
 pub const USERS_TABLE_WALLET_ADDRESS_INDEX: &str = "walletAddress-index";
 pub const USERS_TABLE_FARCASTER_ID_INDEX: &str = "farcasterID-index";
 
-pub const ACCESS_TOKEN_TABLE: &str = "identity-tokens";
-pub const ACCESS_TOKEN_TABLE_PARTITION_KEY: &str = "userID";
-pub const ACCESS_TOKEN_SORT_KEY: &str = "signingPublicKey";
-pub const ACCESS_TOKEN_TABLE_CREATED_ATTRIBUTE: &str = "created";
-pub const ACCESS_TOKEN_TABLE_AUTH_TYPE_ATTRIBUTE: &str = "authType";
-pub const ACCESS_TOKEN_TABLE_VALID_ATTRIBUTE: &str = "valid";
-pub const ACCESS_TOKEN_TABLE_TOKEN_ATTRIBUTE: &str = "token";
+pub mod token_table {
+  pub const NAME: &str = "identity-tokens";
+  pub const PARTITION_KEY: &str = "userID";
+  pub const SORT_KEY: &str = "signingPublicKey";
+  pub const ATTR_CREATED: &str = "created";
+  pub const ATTR_AUTH_TYPE: &str = "authType";
+  pub const ATTR_VALID: &str = "valid";
+  pub const ATTR_TOKEN: &str = "token";
+}
 
 pub const NONCE_TABLE: &str = "identity-nonces";
 pub const NONCE_TABLE_PARTITION_KEY: &str = "nonce";
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
@@ -453,6 +453,9 @@
     debug!(user_id, "Attempting to delete user's devices");
     self.delete_devices_table_rows_for_user(&user_id).await?;
 
+    debug!(user_id, "Attempting to delete user's access tokens");
+    self.delete_all_tokens_for_user(&user_id).await?;
+
     debug!(user_id, "Attempting to delete user");
     match self
       .client
diff --git a/services/identity/src/database/token.rs b/services/identity/src/database/token.rs
--- a/services/identity/src/database/token.rs
+++ b/services/identity/src/database/token.rs
@@ -4,20 +4,17 @@
 use comm_lib::{
   aws::ddb::{
     operation::{get_item::GetItemOutput, put_item::PutItemOutput},
-    types::AttributeValue,
+    types::{AttributeValue, DeleteRequest, WriteRequest},
+  },
+  database::{
+    batch_operations::{batch_write, ExponentialBackoffConfig},
+    DBItemAttributeError, DBItemError, TryFromAttribute,
   },
-  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},
 };
@@ -30,20 +27,16 @@
     user_id: String,
     signing_public_key: String,
   ) -> Result<Option<AccessTokenData>, Error> {
+    use crate::constants::token_table::*;
+
     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(),
-      ),
+      (PARTITION_KEY.to_string(), user_id.clone()),
+      (SORT_KEY.to_string(), signing_public_key.clone()),
     );
     let get_item_result = self
       .client
       .get_item()
-      .table_name(ACCESS_TOKEN_TABLE)
+      .table_name(NAME)
       .set_key(Some(primary_key))
       .consistent_read(true)
       .send()
@@ -54,18 +47,12 @@
         ..
       }) => {
         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),
+          ATTR_CREATED,
+          item.remove(ATTR_CREATED),
         )?;
+        let auth_type = parse_auth_type_attribute(item.remove(ATTR_AUTH_TYPE))?;
+        let valid = parse_valid_attribute(item.remove(ATTR_VALID))?;
+        let access_token = parse_token_attribute(item.remove(ATTR_TOKEN))?;
         Ok(Some(AccessTokenData {
           user_id,
           signing_public_key,
@@ -116,39 +103,41 @@
     &self,
     access_token_data: AccessTokenData,
   ) -> Result<PutItemOutput, Error> {
+    use crate::constants::token_table::*;
+
     let item = HashMap::from([
       (
-        ACCESS_TOKEN_TABLE_PARTITION_KEY.to_string(),
+        PARTITION_KEY.to_string(),
         AttributeValue::S(access_token_data.user_id),
       ),
       (
-        ACCESS_TOKEN_SORT_KEY.to_string(),
+        SORT_KEY.to_string(),
         AttributeValue::S(access_token_data.signing_public_key),
       ),
       (
-        ACCESS_TOKEN_TABLE_TOKEN_ATTRIBUTE.to_string(),
+        ATTR_TOKEN.to_string(),
         AttributeValue::S(access_token_data.access_token),
       ),
       (
-        ACCESS_TOKEN_TABLE_CREATED_ATTRIBUTE.to_string(),
+        ATTR_CREATED.to_string(),
         AttributeValue::S(access_token_data.created.to_rfc3339()),
       ),
       (
-        ACCESS_TOKEN_TABLE_AUTH_TYPE_ATTRIBUTE.to_string(),
+        ATTR_AUTH_TYPE.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(),
+        ATTR_VALID.to_string(),
         AttributeValue::Bool(access_token_data.valid),
       ),
     ]);
     self
       .client
       .put_item()
-      .table_name(ACCESS_TOKEN_TABLE)
+      .table_name(NAME)
       .set_item(Some(item))
       .send()
       .await
@@ -160,42 +149,87 @@
     user_id: String,
     device_id_key: String,
   ) -> Result<(), Error> {
+    use crate::constants::token_table::*;
+
     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),
-      )
+      .table_name(NAME)
+      .key(PARTITION_KEY.to_string(), AttributeValue::S(user_id))
+      .key(SORT_KEY.to_string(), AttributeValue::S(device_id_key))
       .send()
       .await
       .map_err(|e| Error::AwsSdk(e.into()))?;
 
     Ok(())
   }
+
+  pub async fn delete_all_tokens_for_user(
+    &self,
+    user_id: &str,
+  ) -> Result<(), Error> {
+    use crate::constants::token_table::*;
+
+    let primary_keys = self
+      .client
+      .query()
+      .table_name(NAME)
+      .projection_expression("#pk, #sk")
+      .key_condition_expression("#pk = :pk")
+      .expression_attribute_names("#pk", PARTITION_KEY)
+      .expression_attribute_names("#sk", SORT_KEY)
+      .expression_attribute_values(
+        ":pk",
+        AttributeValue::S(user_id.to_string()),
+      )
+      .send()
+      .await
+      .map_err(|e| {
+        error!("Failed to list user's items in tokens table: {:?}", e);
+        Error::AwsSdk(e.into())
+      })?
+      .items
+      .unwrap_or_default();
+
+    let delete_requests = primary_keys
+      .into_iter()
+      .map(|item| {
+        let request = DeleteRequest::builder().set_key(Some(item)).build();
+        WriteRequest::builder().delete_request(request).build()
+      })
+      .collect::<Vec<_>>();
+
+    batch_write(
+      &self.client,
+      NAME,
+      delete_requests,
+      ExponentialBackoffConfig::default(),
+    )
+    .await
+    .map_err(Error::from)?;
+
+    Ok(())
+  }
 }
 
 fn parse_auth_type_attribute(
   attribute: Option<AttributeValue>,
 ) -> Result<AuthType, DBItemError> {
+  use crate::constants::token_table::ATTR_AUTH_TYPE;
+
   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(),
+        ATTR_AUTH_TYPE.to_string(),
         attribute.into(),
         DBItemAttributeError::IncorrectType,
       )),
     }
   } else {
     Err(DBItemError::new(
-      ACCESS_TOKEN_TABLE_AUTH_TYPE_ATTRIBUTE.to_string(),
+      ATTR_AUTH_TYPE.to_string(),
       attribute.into(),
       DBItemAttributeError::Missing,
     ))
@@ -205,15 +239,17 @@
 fn parse_valid_attribute(
   attribute: Option<AttributeValue>,
 ) -> Result<bool, DBItemError> {
+  use crate::constants::token_table::ATTR_VALID;
+
   match attribute {
     Some(AttributeValue::Bool(valid)) => Ok(valid),
     Some(_) => Err(DBItemError::new(
-      ACCESS_TOKEN_TABLE_VALID_ATTRIBUTE.to_string(),
+      ATTR_VALID.to_string(),
       attribute.into(),
       DBItemAttributeError::IncorrectType,
     )),
     None => Err(DBItemError::new(
-      ACCESS_TOKEN_TABLE_VALID_ATTRIBUTE.to_string(),
+      ATTR_VALID.to_string(),
       attribute.into(),
       DBItemAttributeError::Missing,
     )),
@@ -223,15 +259,17 @@
 fn parse_token_attribute(
   attribute: Option<AttributeValue>,
 ) -> Result<String, DBItemError> {
+  use crate::constants::token_table::ATTR_TOKEN;
+
   match attribute {
     Some(AttributeValue::S(token)) => Ok(token),
     Some(_) => Err(DBItemError::new(
-      ACCESS_TOKEN_TABLE_TOKEN_ATTRIBUTE.to_string(),
+      ATTR_TOKEN.to_string(),
       attribute.into(),
       DBItemAttributeError::IncorrectType,
     )),
     None => Err(DBItemError::new(
-      ACCESS_TOKEN_TABLE_TOKEN_ATTRIBUTE.to_string(),
+      ATTR_TOKEN.to_string(),
       attribute.into(),
       DBItemAttributeError::Missing,
     )),