diff --git a/api/config/settings/common.py b/api/config/settings/common.py
index cb5573ed58ddf3edc8ae4df14b7ecf799b0946e4..a836dfdfddb096384bfff92855a34cf478f0aaa6 100644
--- a/api/config/settings/common.py
+++ b/api/config/settings/common.py
@@ -146,6 +146,7 @@ MIDDLEWARE = (
     "django.contrib.auth.middleware.AuthenticationMiddleware",
     "django.contrib.messages.middleware.MessageMiddleware",
     "django.middleware.clickjacking.XFrameOptionsMiddleware",
+    "funkwhale_api.users.middleware.RecordActivityMiddleware",
 )
 
 # MIGRATIONS CONFIGURATION
diff --git a/api/funkwhale_api/users/middleware.py b/api/funkwhale_api/users/middleware.py
new file mode 100644
index 0000000000000000000000000000000000000000..d5e83f0809336c335db7f7c21beaa5ac2b6995f1
--- /dev/null
+++ b/api/funkwhale_api/users/middleware.py
@@ -0,0 +1,9 @@
+class RecordActivityMiddleware:
+    def __init__(self, get_response):
+        self.get_response = get_response
+
+    def __call__(self, request):
+        response = self.get_response(request)
+        if hasattr(request, "user") and request.user.is_authenticated:
+            request.user.record_activity()
+        return response
diff --git a/api/funkwhale_api/users/migrations/0008_auto_20180617_1531.py b/api/funkwhale_api/users/migrations/0008_auto_20180617_1531.py
new file mode 100644
index 0000000000000000000000000000000000000000..b731e327951573b3092d098ab9c9b3c0dfcdf9df
--- /dev/null
+++ b/api/funkwhale_api/users/migrations/0008_auto_20180617_1531.py
@@ -0,0 +1,23 @@
+# Generated by Django 2.0.6 on 2018-06-17 15:31
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('users', '0007_auto_20180524_2009'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='user',
+            name='last_activity',
+            field=models.DateTimeField(blank=True, default=None, null=True),
+        ),
+        migrations.AlterField(
+            model_name='user',
+            name='permission_library',
+            field=models.BooleanField(default=False, help_text='Manage library, delete files, tracks, artists, albums...', verbose_name='Manage library'),
+        ),
+    ]
diff --git a/api/funkwhale_api/users/models.py b/api/funkwhale_api/users/models.py
index caf1e452bbcab42ff52587a50d35c6039fdf132c..d37656c1116fc57db482e71d7c819e45efa873c2 100644
--- a/api/funkwhale_api/users/models.py
+++ b/api/funkwhale_api/users/models.py
@@ -2,6 +2,7 @@
 from __future__ import absolute_import, unicode_literals
 
 import binascii
+import datetime
 import os
 import uuid
 
@@ -9,6 +10,7 @@ from django.conf import settings
 from django.contrib.auth.models import AbstractUser
 from django.db import models
 from django.urls import reverse
+from django.utils import timezone
 from django.utils.encoding import python_2_unicode_compatible
 from django.utils.translation import ugettext_lazy as _
 
@@ -75,6 +77,8 @@ class User(AbstractUser):
         default=False,
     )
 
+    last_activity = models.DateTimeField(default=None, null=True, blank=True)
+
     def __str__(self):
         return self.username
 
@@ -117,3 +121,16 @@ class User(AbstractUser):
 
     def get_activity_url(self):
         return settings.FUNKWHALE_URL + "/@{}".format(self.username)
+
+    def record_activity(self):
+        """
+        Simply update the last_activity field if current value is too old
+        than a threshold. This is useful to keep a track of inactive accounts.
+        """
+        current = self.last_activity
+        delay = 60 * 15  # fifteen minutes
+        now = timezone.now()
+
+        if current is None or current < now - datetime.timedelta(seconds=delay):
+            self.last_activity = now
+            self.save(update_fields=["last_activity"])
diff --git a/api/tests/conftest.py b/api/tests/conftest.py
index 40203ee3d472693d7304d92ab68094949535fab9..aa36e1f76f60d836a33b0c82a44be55a4f3cb372 100644
--- a/api/tests/conftest.py
+++ b/api/tests/conftest.py
@@ -7,6 +7,7 @@ import pytest
 import requests_mock
 from django.contrib.auth.models import AnonymousUser
 from django.core.cache import cache as django_cache
+from django.utils import timezone
 from django.test import client
 from dynamic_preferences.registries import global_preferences_registry
 from rest_framework import fields as rest_fields
@@ -250,3 +251,10 @@ def to_api_date():
         raise ValueError("Invalid value: {}".format(value))
 
     return inner
+
+
+@pytest.fixture()
+def now(mocker):
+    now = timezone.now()
+    mocker.patch("django.utils.timezone.now", return_value=now)
+    return now
diff --git a/api/tests/users/test_middleware.py b/api/tests/users/test_middleware.py
new file mode 100644
index 0000000000000000000000000000000000000000..fd13df4b31b7e8cefb1fa61347574b43fc6ab727
--- /dev/null
+++ b/api/tests/users/test_middleware.py
@@ -0,0 +1,18 @@
+from funkwhale_api.users import middleware
+
+
+def test_record_activity_middleware(factories, api_request, mocker):
+    m = middleware.RecordActivityMiddleware(lambda request: None)
+    user = factories["users.User"]()
+    record_activity = mocker.patch("funkwhale_api.users.models.User.record_activity")
+    request = api_request.get("/")
+    request.user = user
+    m(request)
+
+    record_activity.assert_called_once_with()
+
+
+def test_record_activity_middleware_no_user(api_request):
+    m = middleware.RecordActivityMiddleware(lambda request: None)
+    request = api_request.get("/")
+    m(request)
diff --git a/api/tests/users/test_models.py b/api/tests/users/test_models.py
index c73a4a1b1a4b4cd351278d7757fb2b0307e3b193..74bb091e54270c4034050b7810e2b6ba3cb4bde3 100644
--- a/api/tests/users/test_models.py
+++ b/api/tests/users/test_models.py
@@ -78,3 +78,20 @@ def test_has_permissions_and(args, perms, expected, factories):
 def test_has_permissions_or(args, perms, expected, factories):
     user = factories["users.User"](**args)
     assert user.has_permissions(*perms, operator="or") is expected
+
+
+def test_record_activity(factories, now):
+    user = factories["users.User"]()
+    assert user.last_activity is None
+
+    user.record_activity()
+
+    assert user.last_activity == now
+
+
+def test_record_activity_does_nothing_if_already(factories, now, mocker):
+    user = factories["users.User"](last_activity=now)
+    save = mocker.patch("funkwhale_api.users.models.User.save")
+    user.record_activity()
+
+    save.assert_not_called()