diff --git a/conftest.py b/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..2ea652bf92537b570818facc57341d33e3b1e18c
--- /dev/null
+++ b/conftest.py
@@ -0,0 +1 @@
+pytest_plugins = ["swh.auth.pytest_plugin"]
diff --git a/docs/index.rst b/docs/index.rst
index 7eccf399d6074270bd8c61d4055b136a1e28e3c3..64cacb2e8f2a54cbc38cd7c3177ada15c0bbe26e 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -17,13 +17,13 @@ To get this token, a dedicated CLI tool is made available when installing
 
 .. code-block:: text
 
-  $ swh web auth
-  Usage: swh web auth [OPTIONS] COMMAND [ARGS]...
+  $ swh auth
+  Usage: swh auth [OPTIONS] COMMAND [ARGS]...
 
-    Authenticate Software Heritage users with OpenID Connect.
+    Software Heritage Authentication tools.
 
-    This CLI tool eases the retrieval of bearer tokens to authenticate a user
-    querying the Software Heritage Web API.
+    This CLI eases the retrieval of a bearer token to authenticate a user
+    querying Software Heritage Web APIs.
 
   Options:
     --oidc-server-url TEXT  URL of OpenID Connect server (default to
@@ -38,10 +38,8 @@ To get this token, a dedicated CLI tool is made available when installing
     -h, --help              Show this message and exit.
 
   Commands:
-    generate-token  Generate a new bearer token for Web API authentication.
-    login           Alias for 'generate-token'
-    logout          Alias for 'revoke-token'
-    revoke-token    Revoke a bearer token used for Web API authentication.
+    generate-token  Generate a new bearer token for a Web API authentication.
+    revoke-token    Revoke a bearer token used for a Web API authentication.
 
 In order to get your tokens, you need to use the ``generate-token`` subcommand of
 the CLI tool by passing your username as argument. You will be prompted
@@ -50,7 +48,7 @@ offline session will be created and token will be dumped to standard output.
 
 .. code-block:: text
 
-  $ swh web auth generate-token <username>
+  $ swh auth --client-id swh-web generate-token <username>
   Password:
   eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJmNjMzMD...
 
@@ -81,7 +79,7 @@ to perform that task.
 
 .. code-block:: text
 
-  $ swh web auth revoke-token $REFRESH_TOKEN
+  $ swh auth --client-id swh-web revoke-token $REFRESH_TOKEN
   Token successfully revoked.
 
 API Reference
diff --git a/requirements-swh.txt b/requirements-swh.txt
index 7d8c1a3d3fcfcf791dfb33f70112eac458e9c765..4ee1eb0996f89e6e09e1038fb589d5806bd18cdb 100644
--- a/requirements-swh.txt
+++ b/requirements-swh.txt
@@ -1,3 +1,4 @@
 # Add here internal Software Heritage dependencies, one per line.
+swh.auth >= 0.6
 swh.core >= 0.3
 swh.model
diff --git a/setup.py b/setup.py
index 5c0813281d59f8ee15e54f3545fc76331362baaf..1c5a1d73425cc02b8c1b77a74f08b601f5f61101 100755
--- a/setup.py
+++ b/setup.py
@@ -69,6 +69,6 @@ setup(
     },
     entry_points="""
         [swh.cli.subcommands]
-        auth=swh.web.client.cli
+        web=swh.web.client.cli
     """,
 )
diff --git a/swh/web/client/auth.py b/swh/web/client/auth.py
deleted file mode 100644
index a70cd409cab5803ba4aceaa2a1ebffb6b3f768c5..0000000000000000000000000000000000000000
--- a/swh/web/client/auth.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# Copyright (C) 2020  The Software Heritage developers
-# See the AUTHORS file at the top-level directory of this distribution
-# License: GNU General Public License version 3, or any later version
-# See top-level LICENSE file for more information
-
-from typing import Any, Dict
-from urllib.parse import urljoin
-
-import requests
-
-SWH_OIDC_SERVER_URL = "https://auth.softwareheritage.org/auth/"
-SWH_REALM_NAME = "SoftwareHeritage"
-SWH_WEB_CLIENT_ID = "swh-web"
-
-
-class AuthenticationError(Exception):
-    """Authentication related error.
-
-    Example: A bearer token has been revoked.
-
-    """
-
-    pass
-
-
-class OpenIDConnectSession:
-    """
-    Simple class wrapping requests sent to an OpenID Connect server.
-
-    Args:
-        oidc_server_url: URL of OpenID Connect server
-        realm_name: name of the OpenID Connect authentication realm
-        client_id: OpenID Connect client identifier in the realm
-    """
-
-    def __init__(
-        self,
-        oidc_server_url: str = SWH_OIDC_SERVER_URL,
-        realm_name: str = SWH_REALM_NAME,
-        client_id: str = SWH_WEB_CLIENT_ID,
-    ):
-        realm_url = urljoin(oidc_server_url, f"realms/{realm_name}/")
-        self.client_id = client_id
-        self.token_url = urljoin(realm_url, "protocol/openid-connect/token/")
-        self.logout_url = urljoin(realm_url, "protocol/openid-connect/logout/")
-
-    def login(self, username: str, password: str) -> Dict[str, Any]:
-        """
-        Login and create new offline OpenID Connect session.
-
-        Args:
-            username: an existing username in the realm
-            password: password associated to username
-
-        Returns:
-            The OpenID Connect session info
-        """
-        return requests.post(
-            url=self.token_url,
-            data={
-                "grant_type": "password",
-                "client_id": self.client_id,
-                "scope": "openid offline_access",
-                "username": username,
-                "password": password,
-            },
-        ).json()
-
-    def logout(self, token: str):
-        """
-        Logout from an offline OpenID Connect session and invalidate
-        previously emitted tokens.
-
-        Args:
-            token: a bearer token retrieved after login
-        """
-        requests.post(
-            url=self.logout_url,
-            data={
-                "client_id": self.client_id,
-                "scope": "openid",
-                "refresh_token": token,
-            },
-        )
diff --git a/swh/web/client/cli.py b/swh/web/client/cli.py
index 29c9186cf00fde537c602e576edea433939a5acb..19741542269ef30d64fe3e1528e950edacca1997 100644
--- a/swh/web/client/cli.py
+++ b/swh/web/client/cli.py
@@ -11,6 +11,9 @@ from typing import Any, Dict, List
 import click
 from click.core import Context
 
+from swh.auth.cli import auth as auth_cli
+from swh.auth.cli import generate_token as auth_generate_token
+from swh.auth.cli import revoke_token as auth_revoke_token
 from swh.core.cli import swh as swh_cli_group
 
 CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
@@ -178,48 +181,25 @@ def submit_request(ctx, delimiter: str) -> None:
     print(json.dumps(processed_origins))
 
 
-@web.group(name="auth", context_settings=CONTEXT_SETTINGS)
-@click.option(
-    "--oidc-server-url",
-    "oidc_server_url",
-    default="https://auth.softwareheritage.org/auth/",
-    help=(
-        "URL of OpenID Connect server (default to "
-        '"https://auth.softwareheritage.org/auth/")'
-    ),
-)
-@click.option(
-    "--realm-name",
-    "realm_name",
-    default="SoftwareHeritage",
-    help=(
-        "Name of the OpenID Connect authentication realm "
-        '(default to "SoftwareHeritage")'
-    ),
-)
-@click.option(
-    "--client-id",
-    "client_id",
-    default="swh-web",
-    help=("OpenID Connect client identifier in the realm " '(default to "swh-web")'),
-)
+def _forward_context(ctx: Context, *args, **kwargs):
+    ctx.forward(*args, **kwargs)
+
+
+@web.group(name="auth", context_settings=CONTEXT_SETTINGS, deprecated=True)
 @click.pass_context
-def auth(ctx: Context, oidc_server_url: str, realm_name: str, client_id: str):
+def auth(ctx: Context):
     """
     Authenticate Software Heritage users with OpenID Connect.
 
     This CLI tool eases the retrieval of a bearer token to authenticate
     a user querying the Software Heritage Web API.
-    """
-    from swh.web.client.auth import OpenIDConnectSession
 
-    ctx.ensure_object(dict)
-    ctx.obj["oidc_session"] = OpenIDConnectSession(
-        oidc_server_url, realm_name, client_id
-    )
+    That command group is deprecated, use ``swh auth`` instead.
+    """
+    _forward_context(ctx, auth_cli, client_id="swh-web")
 
 
-@auth.command("generate-token")
+@auth.command("generate-token", deprecated=True)
 @click.argument("username")
 @click.pass_context
 def generate_token(ctx: Context, username: str):
@@ -236,28 +216,10 @@ def generate_token(ctx: Context, username: str):
     token has a much longer expiration time than classical OIDC
     sessions (usually several dozens of days).
     """
-    from getpass import getpass
-
-    password = getpass()
-
-    oidc_info = ctx.obj["oidc_session"].login(username, password)
-    if "refresh_token" in oidc_info:
-        print(oidc_info["refresh_token"])
-    else:
-        print(oidc_info)
+    _forward_context(ctx, auth_generate_token, username=username)
 
 
-@auth.command("login", deprecated=True)
-@click.argument("username")
-@click.pass_context
-def login(ctx: Context, username: str):
-    """
-    Alias for 'generate-token'
-    """
-    ctx.forward(generate_token)
-
-
-@auth.command("revoke-token")
+@auth.command("revoke-token", deprecated=True)
 @click.argument("token")
 @click.pass_context
 def revoke_token(ctx: Context, token: str):
@@ -268,15 +230,4 @@ def revoke_token(ctx: Context, token: str):
 
     The token is definitely revoked after that operation.
     """
-    ctx.obj["oidc_session"].logout(token)
-    print("Token successfully revoked.")
-
-
-@auth.command("logout", deprecated=True)
-@click.argument("token")
-@click.pass_context
-def logout(ctx: Context, token: str):
-    """
-    Alias for 'revoke-token'
-    """
-    ctx.forward(revoke_token)
+    _forward_context(ctx, auth_revoke_token, token=token)
diff --git a/swh/web/client/tests/test_cli.py b/swh/web/client/tests/test_cli.py
index a7bee0f6d5a51a828aa4a3b7ddb24b0c988a15f6..6d48877453a84e3920363c0fd6382d60d73dda5b 100644
--- a/swh/web/client/tests/test_cli.py
+++ b/swh/web/client/tests/test_cli.py
@@ -8,53 +8,37 @@ import os
 
 from click.testing import CliRunner
 
-from swh.web.client.cli import auth, web
+from swh.web.client.cli import auth_cli, auth_generate_token, auth_revoke_token, web
 
 runner = CliRunner()
 
-oidc_profile = {
-    "access_token": "some-access-token",
-    "expires_in": 600,
-    "refresh_expires_in": 0,
-    "refresh_token": "some-refresh-token",
-    "token_type": "bearer",
-    "session_state": "some-state",
-    "scope": "openid email profile offline_access",
-}
-
 
 def test_auth_generate_token(mocker):
-    mock_getpass = mocker.patch("getpass.getpass")
-    mock_getpass.return_value = "password"
-    mock_oidc_session = mocker.patch("swh.web.client.auth.OpenIDConnectSession")
-    mock_login = mock_oidc_session.return_value.login
-    mock_login.return_value = oidc_profile
-
-    for command in ("generate-token", "login"):
-        mock_login.side_effect = None
-        result = runner.invoke(auth, [command, "username"], input="password\n")
-        assert result.exit_code == 0
-        assert oidc_profile["refresh_token"] in result.output
-
-        mock_login.side_effect = Exception("Auth error")
-
-        result = runner.invoke(auth, [command, "username"], input="password\n")
-        assert result.exit_code == 1
+    forward_context = mocker.patch("swh.web.client.cli._forward_context")
+    runner.invoke(web, ["auth", "generate-token", "username"])
+    assert forward_context.call_count == 2
+    ctx = forward_context.call_args_list[0][0][0]
+    ctx2 = forward_context.call_args_list[1][0][0]
+    forward_context.assert_has_calls(
+        [
+            mocker.call(ctx, auth_cli, client_id="swh-web"),
+            mocker.call(ctx2, auth_generate_token, username="username"),
+        ]
+    )
 
 
 def test_auth_revoke_token(mocker):
-
-    mock_oidc_session = mocker.patch("swh.web.client.auth.OpenIDConnectSession")
-    mock_logout = mock_oidc_session.return_value.logout
-
-    for command in ("revoke-token", "logout"):
-        mock_logout.side_effect = None
-        result = runner.invoke(auth, [command, oidc_profile["refresh_token"]])
-        assert result.exit_code == 0
-
-        mock_logout.side_effect = Exception("Auth error")
-        result = runner.invoke(auth, [command, oidc_profile["refresh_token"]])
-        assert result.exit_code == 1
+    forward_context = mocker.patch("swh.web.client.cli._forward_context")
+    runner.invoke(web, ["auth", "revoke-token", "token"])
+    assert forward_context.call_count == 2
+    ctx = forward_context.call_args_list[0][0][0]
+    ctx2 = forward_context.call_args_list[1][0][0]
+    forward_context.assert_has_calls(
+        [
+            mocker.call(ctx, auth_cli, client_id="swh-web"),
+            mocker.call(ctx2, auth_revoke_token, token="token"),
+        ]
+    )
 
 
 def test_save_code_now_through_cli(mocker, web_api_mock, tmp_path, cli_config_path):