diff --git a/authentik/providers/oauth2/models.py b/authentik/providers/oauth2/models.py
index 99bda2c55..0f3ac043b 100644
--- a/authentik/providers/oauth2/models.py
+++ b/authentik/providers/oauth2/models.py
@@ -9,6 +9,7 @@ from typing import Any, Dict, List, Optional, Type
 from urllib.parse import urlparse
 from uuid import uuid4
 
+from dacite import from_dict
 from django.conf import settings
 from django.db import models
 from django.forms import ModelForm
@@ -386,6 +387,18 @@ class AuthorizationCode(ExpiringModel, BaseGrantModel):
         max_length=255, null=True, verbose_name=_("Code Challenge Method")
     )
 
+    @property
+    def c_hash(self):
+        """https://openid.net/specs/openid-connect-core-1_0.html#IDToken"""
+        hashed_code = sha256(self.code.encode("ascii")).hexdigest().encode("ascii")
+        return (
+            base64.urlsafe_b64encode(
+                binascii.unhexlify(hashed_code[: len(hashed_code) // 2])
+            )
+            .rstrip(b"=")
+            .decode("ascii")
+        )
+
     class Meta:
         verbose_name = _("Authorization Code")
         verbose_name_plural = _("Authorization Codes")
@@ -413,19 +426,13 @@ class IDToken:
     auth_time: Optional[int] = None
     acr: Optional[str] = ACR_AUTHENTIK_DEFAULT
 
+    c_hash: Optional[str] = None
+
     nonce: Optional[str] = None
     at_hash: Optional[str] = None
 
     claims: Dict[str, Any] = field(default_factory=dict)
 
-    @staticmethod
-    def from_dict(data: Dict[str, Any]) -> "IDToken":
-        """Reconstruct ID Token from json dictionary"""
-        token = IDToken()
-        for key, value in data.items():
-            setattr(token, key, value)
-        return token
-
     def to_dict(self) -> Dict[str, Any]:
         """Convert dataclass to dict, and update with keys from `claims`"""
         dic = asdict(self)
@@ -454,7 +461,7 @@ class RefreshToken(ExpiringModel, BaseGrantModel):
         """Load ID Token from json"""
         if self._id_token:
             raw_token = json.loads(self._id_token)
-            return IDToken.from_dict(raw_token)
+            return from_dict(IDToken, raw_token)
         return IDToken()
 
     @id_token.setter
diff --git a/authentik/providers/oauth2/views/authorize.py b/authentik/providers/oauth2/views/authorize.py
index c5392ace3..7fdb9917e 100644
--- a/authentik/providers/oauth2/views/authorize.py
+++ b/authentik/providers/oauth2/views/authorize.py
@@ -316,6 +316,12 @@ class OAuthFulfillmentStage(StageView):
                     if "access_token" in query_fragment:
                         id_token.at_hash = token.at_hash
 
+                    if self.params.response_type in [
+                        ResponseTypes.CODE_ID_TOKEN,
+                        ResponseTypes.CODE_ID_TOKEN_TOKEN,
+                    ]:
+                        id_token.c_hash = code.c_hash
+
                     # Check if response_type must include id_token in the response.
                     if self.params.response_type in [
                         ResponseTypes.ID_TOKEN,