Repository: authlib/authlib

Issue:
`id_token` generation does not work with `EdDSA`
It seems `id_token` generation is broken with `EdDSA` jwks.

The `create_half_hash` method is called in the `generate_id_token` method:

https://github.com/authlib/authlib/blob/c9890da9d1f1d5fd7bf551e7cd28facfc64e9b7d/authlib/oidc/core/grants/util.py#L102-L107

However, it seems it does not support `EdDSA` keys:

```python
>>> from authlib.oidc.core.util import create_half_hash
>>> assert create_half_hash("test", "RS256")
>>> assert create_half_hash("test", "EdDSA")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError
```

After investigation, it seems this is because authlib tries to access `hashlib.shaDSA`, which does not exist:

https://github.com/authlib/authlib/blob/c9890da9d1f1d5fd7bf551e7cd28facfc64e9b7d/authlib/oidc/core/util.py#L7-L14


Discussion:

Patch:
diff --git a/authlib/oidc/core/grants/util.py b/authlib/oidc/core/grants/util.py
index 1fa320f4..1906e4e9 100644
--- a/authlib/oidc/core/grants/util.py
+++ b/authlib/oidc/core/grants/util.py
@@ -100,11 +100,15 @@ def generate_id_token(
         payload["amr"] = amr
 
     if code:
-        payload["c_hash"] = to_native(create_half_hash(code, alg))
+        c_hash = create_half_hash(code, alg)
+        if c_hash is not None:
+            payload["c_hash"] = to_native(c_hash)
 
     access_token = token.get("access_token")
     if access_token:
-        payload["at_hash"] = to_native(create_half_hash(access_token, alg))
+        at_hash = create_half_hash(access_token, alg)
+        if at_hash is not None:
+            payload["at_hash"] = to_native(at_hash)
 
     payload.update(user_info)
     return to_native(jwt.encode(header, payload, key))
diff --git a/authlib/oidc/core/util.py b/authlib/oidc/core/util.py
index e5c6024c..9463f95f 100644
--- a/authlib/oidc/core/util.py
+++ b/authlib/oidc/core/util.py
@@ -5,10 +5,14 @@
 
 
 def create_half_hash(s, alg):
-    hash_type = f"sha{alg[2:]}"
-    hash_alg = getattr(hashlib, hash_type, None)
-    if not hash_alg:
-        return None
+    if alg == "EdDSA":
+        hash_alg = hashlib.sha512
+    else:
+        hash_type = f"sha{alg[2:]}"
+        hash_alg = getattr(hashlib, hash_type, None)
+        if not hash_alg:
+            return None
+
     data_digest = hash_alg(to_bytes(s)).digest()
     slice_index = int(len(data_digest) / 2)
     return urlsafe_b64encode(data_digest[:slice_index])
diff --git a/docs/changelog.rst b/docs/changelog.rst
index ace19ac0..9c617af7 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -6,6 +6,13 @@ Changelog
 
 Here you can see the full list of changes between each Authlib release.
 
+Version 1.6.2
+-------------
+
+**Unreleased**
+
+- Fix ``id_token`` generation with `EdDSA` algs.
+
 Version 1.6.1
 -------------
 


Test patch:
diff --git a/tests/core/test_oidc/test_utils.py b/tests/core/test_oidc/test_utils.py
new file mode 100644
index 00000000..b83ae9f2
--- /dev/null
+++ b/tests/core/test_oidc/test_utils.py
@@ -0,0 +1,47 @@
+import pytest
+
+from authlib.jose.rfc7518.ec_key import ECKey
+from authlib.jose.rfc7518.oct_key import OctKey
+from authlib.jose.rfc7518.rsa_key import RSAKey
+from authlib.jose.rfc8037.okp_key import OKPKey
+from authlib.oidc.core import UserInfo
+from authlib.oidc.core.grants.util import generate_id_token
+
+hmac_key = OctKey.generate_key(256)
+rsa_key = RSAKey.generate_key(2048, is_private=True)
+ec_key = ECKey.generate_key("P-256", is_private=True)
+okp_key = OKPKey.generate_key("Ed25519", is_private=True)
+ec_secp256k1_key = ECKey.generate_key("secp256k1", is_private=True)
+
+
+@pytest.mark.parametrize(
+    "alg,key",
+    [
+        ("none", None),
+        ("HS256", hmac_key),
+        ("HS384", hmac_key),
+        ("HS512", hmac_key),
+        ("RS256", rsa_key),
+        ("RS384", rsa_key),
+        ("RS512", rsa_key),
+        ("ES256", ec_key),
+        ("PS256", rsa_key),
+        ("PS384", rsa_key),
+        ("PS512", rsa_key),
+        ("EdDSA", okp_key),
+        ("ES256K", ec_secp256k1_key),
+    ],
+)
+def test_generate_id_token(alg, key):
+    token = {"access_token": "test_token"}
+    user_info = UserInfo({"sub": "123"})
+
+    result = generate_id_token(
+        token=token,
+        user_info=user_info,
+        key=key,
+        iss="https://provider.test",
+        aud="client_id",
+        alg=alg,
+    )
+    assert result is not None