diff --git a/cadetrdm/remote_integration.py b/cadetrdm/remote_integration.py
index 6857d76364d992f713435e2b5fae5904e3ec8fac..3947f5183a2dd9cb64d4df2d93ca91d36d6f5cbc 100644
--- a/cadetrdm/remote_integration.py
+++ b/cadetrdm/remote_integration.py
@@ -1,76 +1,137 @@
 import gitlab
 import github
+import keyring
+from abc import abstractmethod
 
 
-def load_token():
-    """
-    Read the API token from the .token file
-
-    :return:
-    """
-    with open("../.token", "r") as file_handle:
-        token = file_handle.readline()
-    return token
-
-
-def create_gitlab_remote(namespace, name, url=None):
-    token = load_token()
-
-    gl = gitlab.Gitlab(url, private_token=token)
-
-    namespace_id = gl.namespaces.list(get_all=True, search=namespace)[0].id
-    response = gl.projects.create({"name": name, "namespace_id": namespace_id})
-    return response
-
-
-def delete_gitlab_remote(url, namespace, name):
-    token = load_token()
-
-    gl = gitlab.Gitlab(url, private_token=token)
-
-    potential_projects = gl.projects.list(get_all=True, search=[namespace, name])
-
-    for project in potential_projects:
-        if project.name != name:
-            pass
-        if project.namespace["name"] != namespace:
-            pass
-
-        gl.projects.delete(project.id)
-
-
-def create_github_remote(name, namespace=None, url="https://api.github.com"):
-    token = load_token()
-
-    auth = github.Auth.Token(token)
-    g = github.Github(base_url=url, auth=auth)
-    user = g.get_user()
-
-    if namespace is None or namespace == user.login:
-        base = user
-    else:
+class Remote:
+    @staticmethod
+    def load_token(url_options, username):
+        token = None
+        url_options = iter(url_options)
         try:
-            organization = g.get_organization(namespace)
-            base = organization
-        except github.GithubException:
-            raise RuntimeError(f"No organization or user named {namespace} found in {url}")
-
-    response = base.create_repo(
-        name,
-        allow_rebase_merge=True,
-        auto_init=False,
-        has_issues=True,
-        has_projects=False,
-        has_wiki=False,
-        private=False,
-    )
-    return response
-
-
-def delete_github_remote(name, namespace, url="https://api.github.com"):
-    token = load_token()
-
-    auth = github.Auth.Token(token)
-    g = github.Github(base_url=url, auth=auth)
-    repo = g.get_repo(f"{namespace}/{name}")
-    repo.delete()
+            while token is None:
+                token = keyring.get_password(next(url_options), username)
+        except StopIteration:
+            raise RuntimeError(f"No token found in keyring for url {url_options[0]} and username {username}")
+
+        return token
+
+    @abstractmethod
+    def create_remote(self, url, namespace, name, username):
+        return
+
+    @abstractmethod
+    def delete_remote(self, url, namespace, name, username):
+        return
+
+
+class GitLabRemote(Remote):
+
+    @property
+    def url_fallbacks(self):
+        return ["gitlab"]
+
+    def create_remote(self, url, namespace, name, username):
+        """
+        Create remotes on gitlab within the given url / namespace / name. Use the token
+        stored in the keyring under the username and url combination.
+
+        :param namespace:
+        :param name:
+        :param url:
+        :param username:
+        :return:
+        Query response
+        """
+        token = self.load_token([url] + self.url_fallbacks, username)
+        gl = gitlab.Gitlab(url, private_token=token)
+
+        namespace_id = gl.namespaces.list(get_all=True, search=namespace)[0].id
+        response = gl.projects.create({"name": name, "namespace_id": namespace_id})
+        return response
+
+    def delete_remote(self, url, namespace, name, username):
+        """
+        Deletes remotes on gitlab within the given url / namespace / name. Use the token
+        stored in the keyring under the username and url combination.
+
+        :param namespace:
+        :param name:
+        :param url:
+        :param username:
+        :return:
+        None
+        """
+        token = self.load_token([url] + self.url_fallbacks, username)
+        gl = gitlab.Gitlab(url, private_token=token)
+
+        potential_projects = gl.projects.list(get_all=True, search=[namespace, name])
+
+        for project in potential_projects:
+            if project.name != name:
+                pass
+            if project.namespace["name"] != namespace:
+                pass
+
+            gl.projects.delete(project.id)
+        return
+
+
+class GitHubRemote(Remote):
+
+    @property
+    def url_fallbacks(self):
+        return ["https://github.com/", "https://github.com", "github", "github.com"]
+
+    def create_remote(self, name, namespace=None, url="https://api.github.com", username=None):
+        """
+        Create remotes on GitHub within the given url / namespace / name. Use the token
+        stored in the keyring under the username and url combination.
+
+        :param namespace:
+        :param name:
+        :param url:
+        :param username:
+        :return:
+        Query response
+        """
+        if username is None and namespace is not None:
+            username = namespace
+
+        token = self.load_token([url] + self.url_fallbacks, username)
+
+        auth = github.Auth.Token(token)
+        g = github.Github(base_url=url, auth=auth)
+        user = g.get_user()
+
+        if namespace is None or namespace == user.login:
+            base = user
+        else:
+            try:
+                organization = g.get_organization(namespace)
+                base = organization
+            except github.GithubException:
+                raise RuntimeError(f"No organization or user named {namespace} found in {url}")
+
+        response = base.create_repo(
+            name,
+            allow_rebase_merge=True,
+            auto_init=False,
+            has_issues=True,
+            has_projects=False,
+            has_wiki=False,
+            private=False,
+        )
+        return response
+
+    def delete_remote(self, name, namespace, url="https://api.github.com", username=None):
+        if username is None:
+            username = namespace
+
+        token = self.load_token([url] + self.url_fallbacks, username)
+
+        auth = github.Auth.Token(token)
+        g = github.Github(base_url=url, auth=auth)
+        repo = g.get_repo(f"{namespace}/{name}")
+        repo.delete()
diff --git a/cadetrdm/repositories.py b/cadetrdm/repositories.py
index b204ad0fd608595c36f657cd83d1a1dc1f1bef2e..1c5fed3857b9ef8decdb74d8e2e2c6d2278b92b7 100644
--- a/cadetrdm/repositories.py
+++ b/cadetrdm/repositories.py
@@ -15,7 +15,7 @@ from tabulate import tabulate
 
 from cadetrdm.io_utils import recursive_chmod, write_lines_to_file, wait_for_user, init_lfs
 from cadetrdm.jupyter_functionality import Notebook
-from cadetrdm.remote_integration import create_gitlab_remote, create_github_remote
+from cadetrdm.remote_integration import GitHubRemote, GitLabRemote
 from cadetrdm.version import version as cadetrdm_version
 
 try:
@@ -622,36 +622,27 @@ class ProjectRepo(BaseRepo):
         jupytext_lines = ['# Pair ipynb notebooks to py:percent text notebooks', 'formats: "ipynb,py:percent"']
         write_lines_to_file(Path(path_root) / "jupytext.yml", lines=jupytext_lines, open_type="w")
 
-    def create_gitlab_remotes(self, name, namespace, url=None):
+    def create_remotes(self, name, namespace, url=None, username=None):
         """
         Create project in gitlab and add the projects as remotes to the project and output repositories
 
+        :param username:
         :param url:
         :param namespace:
         :param name:
         :return:
         """
-        response_project = create_gitlab_remote(url=url, namespace=namespace, name=name)
-        response_output = create_gitlab_remote(url=url, namespace=namespace, name=name + "_output")
+        if "github" in url:
+            remote = GitHubRemote()
+        else:
+            remote = GitLabRemote()
+
+        response_project = remote.create_remote(url=url, namespace=namespace, name=name, username=username)
+        response_output = remote.create_remote(url=url, namespace=namespace, name=name + "_output", username=username)
         self.add_remote(response_project.ssh_url_to_repo)
         self.output_repo.add_remote(response_output.ssh_url_to_repo)
         self.push(push_all=True)
 
-    def create_github_remotes(self, name, namespace=None, url="https://api.github.com"):
-        """
-        Create project in GitHub and add the projects as remotes to the project and output repositories
-
-        :param namespace:
-        :param name:
-        :param url:
-        :return:
-        """
-        response_project = create_github_remote(namespace=namespace, name=name, url=url)
-        response_output = create_github_remote(namespace=namespace, name=name + "_output", url=url)
-        self.add_remote(response_project.html_url)
-        self.output_repo.add_remote(response_output.html_url)
-        self.push(push_all=True)
-
     def get_new_output_branch_name(self):
         """
         Construct a name for the new branch in the output repository.
diff --git a/docs/source/user_guide/getting-started.md b/docs/source/user_guide/getting-started.md
index fff79444b70c07f8bedbbc01255b0c8d95929734..19c433c44cd4b110371913ee9cb85018c18b4f17 100644
--- a/docs/source/user_guide/getting-started.md
+++ b/docs/source/user_guide/getting-started.md
@@ -23,12 +23,20 @@ The `output_folder_name` can be given optionally. It defaults to `output`.
 
 You can create remotes for both the project and the output repository with one command, using the GitLab or GitHub API.
 
-Using the GitLab API requires you to have created a
-[GitLab Personal Access Token (PAT)](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html)
-and to store it in the `.token` file in the project's root. Then you can run:
+You need to create a
+[GitLab Personal Access Token (PAT)](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html) or [GitHub PAT](https://github.com/settings/tokens?type=beta)
+and to store it in the Python `keyring` using
 
 ```python
-repo.create_gitlab_remotes(
+import keyring
+
+keyring.set_password("e.g. https://jugit.fz-juelich.de/", username, token)
+```
+
+Then you can run:
+
+```python
+repo.create_remotes(
     name="e.g. API_test_project",
     namespace="e.g. r.jaepel",
     url="e.g. https://jugit.fz-juelich.de/",
@@ -38,12 +46,10 @@ repo.create_gitlab_remotes(
 or
 
 ```bash
-cadet-rdm create-gitlab-remotes API_test_project r.jaepel https://jugit.fz-juelich.de/
+cadet-rdm create-remotes name namespace url username
+cadet-rdm create-remotes API_test_project r.jaepel https://jugit.fz-juelich.de/ r.jaepel
 ```
 
-Both functions are also available for the GitHub API, which will require a
-[GitHub PAT](https://github.com/settings/tokens?type=beta):
-`repo.create_github_remotes(name, namespace)` and ` cadet-rdm create-github-remotes name namespace`.
 
 ## Extending GIT-LFS scope
 
diff --git a/setup.cfg b/setup.cfg
index 2560ffbf1ea1be1fdf06faa34321ef59a9d6bdc8..e3b27830093d9fba38a4f00b920024200d2403ea 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -24,6 +24,7 @@ install_requires =
     git-lfs
     click
     tabulate
+    keyring
 
 include_package_data = True
 
diff --git a/tests/test_gitlab_api.py b/tests/test_gitlab_api.py
index 792b023dfb7e4a0a164ffdf97910da21e5c71cdc..15e467ee178a51d31fac587625e803533fc14996 100644
--- a/tests/test_gitlab_api.py
+++ b/tests/test_gitlab_api.py
@@ -6,16 +6,17 @@ import pytest
 from cadetrdm import initialize_repo, ProjectRepo
 from cadetrdm.io_utils import delete_path
 
-from cadetrdm.remote_integration import delete_gitlab_remote, create_gitlab_remote
+from cadetrdm.remote_integration import GitHubRemote, GitLabRemote
 
 
 def test_gitlab_create():
     url = "https://jugit.fz-juelich.de/"
     namespace = "r.jaepel"
     name = "API_test_project"
+    remote = GitLabRemote()
 
     # ensure remote does not exist
-    delete_gitlab_remote(url=url, namespace=namespace, name=name)
+    remote.delete_remote(url=url, namespace=namespace, name=name, username="r.jaepel")
     try:
         delete_path("test_repo_remote")
     except FileNotFoundError:
@@ -23,46 +24,46 @@ def test_gitlab_create():
 
     sleep(3)
 
-    response = create_gitlab_remote(url=url, namespace=namespace, name=name)
+    response = remote.create_remote(url=url, namespace=namespace, name=name, username="r.jaepel")
 
     git.Repo.clone_from(response.ssh_url_to_repo, "test_repo_remote")
     delete_path("test_repo_remote")
 
-    delete_gitlab_remote(url=url, namespace=namespace, name=name)
+    remote.delete_remote(url=url, namespace=namespace, name=name, username="r.jaepel")
 
     with pytest.raises(git.exc.GitCommandError):
         git.Repo.clone_from(response.ssh_url_to_repo, "test_repo_remote")
 
 
-# def test_github_create():
-#     from cadetrdm.remote_integration import delete_github_remote, create_github_remote
-#     namespace = "ronald-jaepel"
-#     name = "API_test_project"
-#
-#     # ensure remote does not exist
-#     try:
-#         delete_github_remote(namespace=namespace, name=name)
-#     except Exception:
-#         pass
-#
-#     try:
-#         delete_path("test_repo_remote")
-#     except FileNotFoundError:
-#         pass
-#
-#     sleep(3)
-#
-#     response = create_github_remote(namespace=namespace, name=name)
-#
-#     sleep(3)
-#
-#     git.Repo.clone_from(response.html_url, "test_repo_remote")
-#     delete_path("test_repo_remote")
-#
-#     delete_github_remote(namespace=namespace, name=name)
-#
-#     with pytest.raises(git.exc.GitCommandError):
-#         git.Repo.clone_from(response.ssh_url_to_repo, "test_repo_remote")
+def test_github_create():
+    namespace = "ronald-jaepel"
+    name = "API_test_project"
+    remote = GitHubRemote()
+
+    # ensure remote does not exist
+    try:
+        remote.delete_remote(namespace=namespace, name=name, username="r.jaepel")
+    except Exception:
+        pass
+
+    try:
+        delete_path("test_repo_remote")
+    except FileNotFoundError:
+        pass
+
+    sleep(3)
+
+    response = remote.create_remote(namespace=namespace, name=name, username="r.jaepel")
+
+    sleep(3)
+
+    git.Repo.clone_from(response.html_url, "test_repo_remote")
+    delete_path("test_repo_remote")
+
+    remote.delete_remote(namespace=namespace, name=name, username="r.jaepel")
+
+    with pytest.raises(git.exc.GitCommandError):
+        git.Repo.clone_from(response.ssh_url, "test_repo_remote")
 
 
 def test_repo_gitlab_integration():
@@ -70,10 +71,11 @@ def test_repo_gitlab_integration():
     namespace = "r.jaepel"
     name = "API_test_project"
     repo_name = "test_repo_remote"
+    remote = GitLabRemote()
 
     # Clean up
-    delete_gitlab_remote(url=url, namespace=namespace, name=name)
-    delete_gitlab_remote(url=url, namespace=namespace, name=name + "_output")
+    remote.delete_remote(url=url, namespace=namespace, name=name, username="r.jaepel")
+    remote.delete_remote(url=url, namespace=namespace, name=name + "_output", username="r.jaepel")
 
     try:
         delete_path("test_repo_remote")
@@ -83,4 +85,4 @@ def test_repo_gitlab_integration():
     initialize_repo(repo_name)
 
     repo = ProjectRepo(repo_name)
-    repo.create_gitlab_remotes(url=url, namespace=namespace, name=name)
+    repo.create_remotes(url=url, namespace=namespace, name=name, username="r.jaepel")