diff --git a/swh/auth/django/utils.py b/swh/auth/django/utils.py
index 40bbf8c4bb761583d91704cae4292f1849b1827c..31f1d8f34356ab1684e3f6206f39aa4eabf73bd2 100644
--- a/swh/auth/django/utils.py
+++ b/swh/auth/django/utils.py
@@ -3,9 +3,11 @@
 # License: GNU Affero General Public License version 3, or any later version
 # See top-level LICENSE file for more information
 
+from datetime import datetime, timedelta
 from typing import Any, Dict, Optional
 
 from swh.auth.django.models import OIDCUser
+from swh.auth.keycloak import KeycloakOpenIDConnect
 
 
 def oidc_user_from_decoded_token(
@@ -55,3 +57,41 @@ def oidc_user_from_decoded_token(
     user.sub = decoded_token["sub"]
 
     return user
+
+
+def oidc_user_from_profile(
+    oidc_client: KeycloakOpenIDConnect, oidc_profile: Dict[str, Any]
+) -> OIDCUser:
+    """Initialize an OIDCUser out of an oidc profile dict.
+
+    Args:
+        oidc_client: KeycloakOpenIDConnect used to discuss with keycloak
+        oidc_profile: OIDC profile retrieved once connected to keycloak
+
+    Returns:
+        OIDCUser instance parsed out of the token received.
+
+    """
+
+    # decode JWT token
+    decoded_token = oidc_client.decode_token(oidc_profile["access_token"])
+
+    # create OIDCUser from decoded token
+    user = oidc_user_from_decoded_token(decoded_token, client_id=oidc_client.client_id)
+
+    # get authentication init datetime
+    auth_datetime = datetime.fromtimestamp(decoded_token["auth_time"])
+    exp_datetime = datetime.fromtimestamp(decoded_token["exp"])
+
+    # compute OIDC tokens expiration date
+    oidc_profile["expires_at"] = exp_datetime
+    oidc_profile["refresh_expires_at"] = auth_datetime + timedelta(
+        seconds=oidc_profile["refresh_expires_in"]
+    )
+
+    # add OIDC profile data to custom User proxy model
+    for key, val in oidc_profile.items():
+        if hasattr(user, key):
+            setattr(user, key, val)
+
+    return user
diff --git a/swh/auth/tests/conftest.py b/swh/auth/tests/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..9172938df84bfcc7444046ea7dda6a03d743c724
--- /dev/null
+++ b/swh/auth/tests/conftest.py
@@ -0,0 +1,13 @@
+# Copyright (C) 2021 The Software Heritage developers
+# See the AUTHORS file at the top-level directory of this distribution
+# License: GNU Affero General Public License version 3, or any later version
+# See top-level LICENSE file for more information
+
+
+from swh.auth.pytest_plugin import keycloak_mock_factory
+from swh.auth.tests.sample_data import CLIENT_ID, REALM_NAME, SERVER_URL
+
+# keycloak fixture used within tests (cf. test_keycloak.py, test_utils.py)
+keycloak_mock = keycloak_mock_factory(
+    server_url=SERVER_URL, realm_name=REALM_NAME, client_id=CLIENT_ID,
+)
diff --git a/swh/auth/tests/test_keycloak.py b/swh/auth/tests/test_keycloak.py
index 3bcda9f4b8806bf3385b22df79d2649ce8833c8e..2546b5f77b4cc5d04149659abb06ee707afdfe1c 100644
--- a/swh/auth/tests/test_keycloak.py
+++ b/swh/auth/tests/test_keycloak.py
@@ -12,22 +12,9 @@ import pytest
 import yaml
 
 from swh.auth.keycloak import KeycloakOpenIDConnect
-from swh.auth.pytest_plugin import keycloak_mock_factory
-from swh.auth.tests.sample_data import (
-    CLIENT_ID,
-    DECODED_TOKEN,
-    OIDC_PROFILE,
-    REALM_NAME,
-    SERVER_URL,
-    USER_INFO,
-)
+from swh.auth.tests.sample_data import CLIENT_ID, DECODED_TOKEN, OIDC_PROFILE, USER_INFO
 from swh.core.config import read
 
-# Make keycloak fixture to use for tests below.
-keycloak_mock = keycloak_mock_factory(
-    server_url=SERVER_URL, realm_name=REALM_NAME, client_id=CLIENT_ID,
-)
-
 
 def test_keycloak_well_known(keycloak_mock):
     well_known_result = keycloak_mock.well_known()
diff --git a/swh/auth/tests/test_utils.py b/swh/auth/tests/test_utils.py
index 377da2464ab0026b3567a269a81e7536d37d0df7..64954b751b3642d9a999344b4a906bf599ddd4e9 100644
--- a/swh/auth/tests/test_utils.py
+++ b/swh/auth/tests/test_utils.py
@@ -4,9 +4,10 @@
 # See top-level LICENSE file for more information
 
 from copy import copy
+from datetime import datetime
 
-from swh.auth.django.utils import oidc_user_from_decoded_token
-from swh.auth.tests.sample_data import CLIENT_ID, DECODED_TOKEN
+from swh.auth.django.utils import oidc_user_from_decoded_token, oidc_user_from_profile
+from swh.auth.tests.sample_data import CLIENT_ID, DECODED_TOKEN, OIDC_PROFILE
 
 
 def test_oidc_user_from_decoded_token():
@@ -39,3 +40,24 @@ def test_oidc_user_from_decoded_token2():
     assert user.is_staff is True
     assert user.permissions == {"read-api"}
     assert user.sub == "feacd344-b468-4a65-a236-14f61e6b7200"
+
+
+def test_oidc_user_from_profile(keycloak_mock):
+    date_now = datetime.now()
+
+    user = oidc_user_from_profile(keycloak_mock, OIDC_PROFILE)
+
+    assert user.id == 338521271020811424925120118444075479552
+    assert user.username == "johndoe"
+    assert user.password == ""
+    assert user.first_name == "John"
+    assert user.last_name == "Doe"
+    assert user.email == "john.doe@example.com"
+    assert user.is_staff is False
+    assert user.permissions == set()
+    assert user.sub == "feacd344-b468-4a65-a236-14f61e6b7200"
+
+    assert isinstance(user.expires_at, datetime)
+    assert date_now <= user.expires_at
+    assert isinstance(user.refresh_expires_at, datetime)
+    assert date_now <= user.refresh_expires_at