diff --git a/djblets/auth/ratelimit.py b/djblets/auth/ratelimit.py
new file mode 100644
index 0000000000000000000000000000000000000000..41162e85961172bcbd78b80e37ea72c140c53014
--- /dev/null
+++ b/djblets/auth/ratelimit.py
@@ -0,0 +1,78 @@
+"""Utility for rate-limiting login attempts"""
+
+from __future__ import unicode_literals
+
+import re
+import time
+
+from django.conf import settings
+from django.core.cache import cache # or get_cache if you have more than 1 type of cache setting.
+from django.core.exceptions import ImproperlyConfigured
+
+
+periods = {
+    's': 1,
+    'm': 60,
+    'h': 60 * 60,
+    'd': 24 * 60 * 60,
+}
+
+rate_re = re.compile('([\d]+)/([\d]*)([smhd])?')
+
+def user_or_ip(request):
+    ''' 
+    Obtain user ID or IP address from request.
+
+    If the user is authenticated, the user ID will be returned.
+    Otherwise, the IP address of the client is returned instead.
+
+    Args:
+        request (HttpRequest):
+            The HTTP request from the client.
+
+    Returns:
+        String value for user ID or IP address.
+    '''
+    if request.user.is_authenticated():
+        return str(request.user.pk)
+    
+    return request.META['REMOTE_ADDR']
+
+def _split_rate(rate):
+    
+    if isinstance(rate, tuple):
+        return rate
+    
+    count, multi, period = rate_re.match(rate).groups()
+    count = int(count)
+    
+    if not period:
+        period = 's'
+    
+    seconds = periods[period.lower()]
+    
+    if multi:
+        seconds = seconds * int(multi)
+
+    return count, seconds
+
+def is_ratelimited(request, key=None, rate=LOGIN_LIMIT_RATE, increment=False):
+
+    if callable(rate):
+        rate = rate(group, request)
+
+    # Obtain number of times login attempt has been made to check if ratelimit
+    # needs to be applied.
+    usage = get_usage_count(request, key, rate, increment)
+
+def get_usage_count(request, key=None, rate=LOGIN_LIMIT_RATE, increment=False):
+
+    if not key:
+        raise ImproperlyConfigured('Ratelimit key must be specified')
+
+    # Obtain specified time limit and period (ex: s, m, h, d)
+    limit, period = _split_rate(rate)
+
+    # TODO: Next step: Understand how the cache works in Djblets
+    #       Cannot figure out how caching was done in django-ratelimit, therefore cannot replicate.
+    #       They used get_cache instead of cache.
\ No newline at end of file
diff --git a/djblets/settings.py b/djblets/settings.py
index 18e42d8f54a8e634bb8c604b046e5df1cff38969..e298735658ebf9206d190b56ebc8ebde44e1bc72 100644
--- a/djblets/settings.py
+++ b/djblets/settings.py
@@ -21,6 +21,7 @@ DJBLETS_ROOT = os.path.abspath(os.path.dirname(__file__))
 HTDOCS_ROOT = os.path.join(DJBLETS_ROOT, 'htdocs')
 STATIC_ROOT = os.path.join(HTDOCS_ROOT, 'static')
 STATIC_URL = '/'
+LOGIN_LIMIT_RATE = '5/m'
 
 STATICFILES_DIRS = (
     os.path.join(DJBLETS_ROOT, 'static'),
