diff --git a/app/__init__.py b/app/__init__.py
index c6df42939037fa067d81c769619c5e6d12069444..0b14dc23e8ba4503acc4bc2b627d987b1ffa72f0 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -39,8 +39,9 @@ def create_app(config_class=Config):
         message_queue=app.config.get('NOPAQUE_SOCKETIO_MESSAGE_QUEUE_URI')
     )
 
-    from .utils import HashidConverter
+    from .utils import HashidConverter, permission_context_processor
     app.url_map.converters['hashid'] = HashidConverter
+    app.context_processor(permission_context_processor)
 
     from .events import socketio as socketio_events
     from .events import sqlalchemy as sqlalchemy_events
diff --git a/app/decorators.py b/app/decorators.py
index 988091227edd24aabc18ca8457f776ce237e0cb4..74b37a66be2072547d2dbb64a2b04a6722a2729b 100644
--- a/app/decorators.py
+++ b/app/decorators.py
@@ -2,16 +2,47 @@ from flask import abort, current_app
 from flask_login import current_user
 from functools import wraps
 from threading import Thread
+from .models import Permission
+
+
+def permission_required(permission):
+    def decorator(f):
+        @wraps(f)
+        def decorated_function(*args, **kwargs):
+            if not current_user.can(permission):
+                abort(403)
+            return f(*args, **kwargs)
+        return decorated_function
+    return decorator
 
 
 def admin_required(f):
+    return permission_required(Permission.ADMINISTRATE)(f)
+
+
+def socketio_login_required(f):
     @wraps(f)
-    def wrapped(*args, **kwargs):
-        if current_user.is_administrator:
+    def decorated_function(*args, **kwargs):
+        if current_user.is_authenticated:
             return f(*args, **kwargs)
         else:
-            abort(403)
-    return wrapped
+            return {'code': 401, 'msg': 'Unauthorized'}
+    return decorated_function
+
+
+def socketio_permission_required(permission):
+    def decorator(f):
+        @wraps(f)
+        def decorated_function(*args, **kwargs):
+            if not current_user.can(permission):
+                return {'code': 403, 'msg': 'Forbidden'}
+            return f(*args, **kwargs)
+        return decorated_function
+    return decorator
+
+
+def socketio_admin_required(f):
+    return socketio_permission_required(Permission.ADMINISTRATE)(f)
 
 
 def background(f):
diff --git a/app/models.py b/app/models.py
index 1572a14486b55d4bbc6c51cf911ba56def9f2940..a00db66d20cbe3e945f9680a1ee7a9c3f6d907e5 100644
--- a/app/models.py
+++ b/app/models.py
@@ -38,8 +38,9 @@ class Permission(enum.IntEnum):
     Defines User permissions as integers by the power of 2. User permission
     can be evaluated using the bitwise operator &.
     '''
-    ADMINISTRATE = 1
-    USE_API = 2
+    ADMINISTRATE = 4
+    CONTRIBUTE = 2
+    USE_API = 1
 
 
 class Role(HashidMixin, db.Model):
@@ -93,7 +94,13 @@ class Role(HashidMixin, db.Model):
     def insert_roles():
         roles = {
             'User': [],
-            'Administrator': [Permission.USE_API, Permission.ADMINISTRATE]
+            'API user': [Permission.USE_API],
+            'Contributor': [Permission.CONTRIBUTE],
+            'Administrator': [
+                Permission.ADMINISTRATE,
+                Permission.CONTRIBUTE,
+                Permission.USE_API
+            ]
         }
         default_role_name = 'User'
         for role_name, permissions in roles.items():
diff --git a/app/templates/_sidenav.html.j2 b/app/templates/_sidenav.html.j2
index aaa71647f0aaa3faeda814896ba6b8a7de904405..e50e345ab7ea83deedd946708d499cd8ede25cfa 100644
--- a/app/templates/_sidenav.html.j2
+++ b/app/templates/_sidenav.html.j2
@@ -22,10 +22,12 @@
   <li><a class="subheader">Account</a></li>
   <li><a href="{{ url_for('settings.index') }}"><i class="material-icons">settings</i>Settings</a></li>
   <li><a href="{{ url_for('auth.logout') }}">Log out</a></li>
-  {% if current_user.is_administrator() %}
+  {% if current_user.can(Permission.ADMINISTRATE) %}
   <li><div class="divider"></div></li>
-  <li><a class="subheader">Administration</a></li>
+  <li><a class="subheader">Specials</a></li>
   <li><a href="{{ url_for('admin.index') }}"><i class="material-icons">admin_panel_settings</i>Administration</a></li>
+  {% endif %}
+  {% if current_user.can(Permission.USE_API) %}
   <li><a href="{{ url_for('api.doc') }}"><i class="material-icons">api</i>API</a></li>
   {% endif %}
 </ul>
diff --git a/app/utils.py b/app/utils.py
index a320d4bb7a1b389fd6a30a3e46126aedcd608d30..04ca4a45ad39506f3c82bf7ef1cfaf55459f0d0d 100644
--- a/app/utils.py
+++ b/app/utils.py
@@ -1,5 +1,6 @@
 from app import hashids
 from werkzeug.routing import BaseConverter
+from .models import Permission
 
 
 class HashidConverter(BaseConverter):
@@ -8,3 +9,7 @@ class HashidConverter(BaseConverter):
 
     def to_url(self, value):
         return hashids.encode(value)
+
+
+def permission_context_processor():
+    return {'Permission': Permission}