diff --git a/labelbot/auth.py b/labelbot/auth.py
new file mode 100644
index 0000000..8ad32b3
--- /dev/null
+++ b/labelbot/auth.py
@@ -0,0 +1,52 @@
+"""Handles authentication tokens with the GitHub API.
+
+.. module:: auth
+    :synopsis: Functions for generating tokens used for authentication with GitHub.
+.. moduleauthor:: Lars Hummelgren <larshum@kth.se> & Joakim Croona <jcroona@kth.se>
+
+"""
+
+import datetime
+import http.client
+import json
+import jwcrypto
+import python_jwt
+import requests
+
+USER_AGENT = "label-bot"
+
+
+def generate_jwt_token(private_pem: str, app_id: int) -> str:
+    """Generates a JWT token valid for 10 minutes using the private key.
+
+    Args:
+        private_pem: the private key that is used to generate a JWT
+        app_id the Application id
+    Returns:
+        The JWT that was generated using the private key and the app id
+    """
+    private_key = jwcrypto.jwk.JWK.from_pem(private_pem.encode(encoding="utf8"))
+    payload = {"iss": app_id}
+    duration = datetime.timedelta(minutes=10)
+    return python_jwt.generate_jwt(payload, private_key, "RS256", duration)
+
+
+def generate_installation_access_token(jwt_token: str, installation_id):
+    """Generates an installation access token using a JWT token and an installation id.
+
+    An installation access token is valid for 1 hour.
+
+    Args:
+        jwt_token: a valid JWT token
+        installation_id: the installation id of the app.
+    Returns:
+        An installation access token from the GitHub API
+    """
+    headers = {
+        "Authorization": f"Bearer {jwt_token}",
+        "Accept": "application/vnd.github.machine-man-preview+json",
+        "User-Agent": USER_AGENT,
+    }
+    url = f"https://api.github.com/app/installations/{installation_id}/access_tokens"
+    r = requests.post(url, headers=headers)
+    return r.json()["token"]

diff --git a/tests/test_auth.py b/tests/test_auth.py
new file mode 100644
index 0000000..528d56d
--- /dev/null
+++ b/tests/test_auth.py
@@ -0,0 +1,35 @@
+from labelbot import auth
+
+rsa_key = """-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAxgHlyqhl8bMNT8opV631hJy1aJ7guF7iDfBMa+V3kKzo5OUu
+h5VGQ5xWQPZDf27brxgL0ywvfYzw+YaP6TaMcmUR8nzE+gr9B+GOc92GtVrZd4dd
+o2CL/wLpANUwMZ2sCWWHg3sf5S5KRbg0WUPLk8dqnwINJKUnd1uZW4M76FMJxg92
+iQIMgdngEoos+bGE0X8mkClics0hMxZcUUsATV9YnrOztnx356/4yaZaRySp7gqd
+bzuDHgi4kJi228eqWH12O6Q2tooOb1WZHUdOjlcYyjulipNiA9G0SnanN9YxmNGc
++0iLRQvlvuHPxA490d/WR+iMXAGHmUN40PYucwIDAQABAoIBAQC6ze2CPU84MOld
+HmfUnXzk6IdJNaLirjlsVwlyPaGIr2hlEkbMiSsp/CNv1CWn4umFDhWR27zOIRry
+/l1k8x6bifjdoZKgJ5/CQK0JaLR/Aj/qQZ441YweQRubuoVmOc+ladDoXU+hAwP1
+NKzGjmxQdjGD5AaMTPen5pYPPQdWzN5AvTH/Q3FIFsGAJ/JZIj3CjxJ5zyDdlYvJ
+PKW/3E5DYSCVyjopjZhiNDHIa8Ur8e/b0S7EmxtRZc3IdV1nLAgRCkOkOZo9i0D8
+RN+Ro5Aiw/DUcYl4VzVxdikAASu9+Fey9NFDceeClKnIe/L5FXHjXR8VXuaw60AY
+htI6g7LpAoGBAPSsiu1Fqtp+s07nHeeChTqPgpSSx7ihhEgZ/TqCElEl4xG4mkMg
+vaSX9nS5Mm/cUF3mD40KwS+eaBxIVtk7uyQ80lBEc/lC/7txGKVCFWB+x7ChxPxI
+HLA0pBg/O5g9HLtFXGDJn4WwbFU/j1luPuwNOFD/yqTNpQUiubuq9is9AoGBAM8s
+V637me/rkyPDP0/up93Ox3szguIv03CaKqvL03ZJU/DZIMwtQgpvIKrBFhlQMG1Q
+KkmgeCgpmLjgCIElXqLldur2TSag6Mtw2suomjELLNUpgnDs1hdq1xBcZCDyyGCj
+F+AUjEun2+9TVLeGxvjtY1rB2a2Kc/k3XQXi8htvAoGBALYWdqnQUC+dXUpaO+og
+O5ByXwa9Gb+xDGYwWUGirYkQviVhEgTlJ92HblY9wWh4OKM45NfdahpLNEXgHCo9
+BrAYR5iO6RRXJUTVDTdnr8sJbwlnytbJv/fupTSaUnqg+HHyU6aARqTSwDzNOZyf
+rFo2GHRHeQMfPDFPP5SHzf4tAoGBAKsizRcKhMH8zqI3MkCcO4ztuDkcdxzTNw2I
+PIGHsRnAPxfwtLgVFr25yLllIRUt+aMDGruRVFCQ8/icEEpmjUNw3AgCB/9F4qfT
+hNnBYAXtXk6DqJ4R9lSHzDpWp9vT0hSKBTn0n2QLuJF9O7kTG6AbsPwSr/c8LMsM
+ocowC6D/AoGAOEy3olvgdY9lWr6zHdK745qnFK2hHuAT4LEw+37/1WucNIXxItM8
+RdDCSLSlSxkpOjWFJPhdW19i/9O4QNNwAF5wCtlIFeN+HF24LwDF7I/Xj26pIKBj
+8lEnfws5Dn7I9sG0wBn+5px52jgOqiKzjy3jcIENBRO6i1OZ8c07NmM=
+-----END RSA PRIVATE KEY-----"""
+
+
+def test_auth_jwt_token():
+    """Tests that jwt_token is truthy"""
+    result = auth.generate_jwt_token(rsa_key, 12334)
+    assert result

Access token for privileged operations with the GitHub API
As we want to set labels on issues (a privileged operation) via the API, we need to generate an access token. [The process is described here](https://developer.github.com/apps/building-github-apps/authenticating-with-github-apps/#generating-a-private-key). There's a Python package [python_jwt](https://pypi.org/project/python_jwt/) that can do the hard parts for us. I've got an example of how to do this in an old project (although I wasn't the one who implemented it, so I'm not privvy to the details).

Once an access token has been generated, it's valid for 1 hour. If we want to optimize for performance, we could store the token for 1 hour in DynamoDB using the [TTL functionality](https://aws.amazon.com/about-aws/whats-new/2017/02/amazon-dynamodb-now-supports-automatic-item-expiration-with-time-to-live-ttl/). Won't be necessary for a prototype, but could be cool if we've got time left to do it.
tests/test_auth.py::test_auth_jwt_token