diff --git a/pygitweb/auth.schema.json b/pygitweb/auth.schema.json
index d2b25cc..d0ead41 100644
--- a/pygitweb/auth.schema.json
+++ b/pygitweb/auth.schema.json
@@ -29,9 +29,10 @@
       },
       "oauth_allowed_emails": {
         "type": "array",
+        "description": "OAuth sign-in allowlist. Empty allows all. Entries support fnmatch globs (*, ?) on the normalized e-mail.",
         "items": {
-          "required": [],
-          "properties": {}
+          "type": "string",
+          "minLength": 1
         }
       },
       "oauth_permissions": {
diff --git a/pygitweb/auth_oauth.py b/pygitweb/auth_oauth.py
index 7818881..5c283f9 100644
--- a/pygitweb/auth_oauth.py
+++ b/pygitweb/auth_oauth.py
@@ -9,6 +9,7 @@ classes only extract credentials from requests; they do not implement this redir
 from __future__ import annotations
 
 import base64
+import fnmatch
 import hashlib
 import hmac
 import json
@@ -139,8 +140,9 @@ def is_oauth_email_allowed(config: AuthConfig, email: str | None) -> bool:
 		return True
 	if not email:
 		return False
-	allowed = {e.strip().lower() for e in config.oauth_allowed_emails if e.strip()}
-	return email.strip().lower() in allowed
+	normalized = email.strip().lower()
+	patterns = [p.strip().lower() for p in config.oauth_allowed_emails if p.strip()]
+	return any(fnmatch.fnmatchcase(normalized, pattern) for pattern in patterns)
 
 
 async def _exchange_code(
diff --git a/pygitweb/auth_test.py b/pygitweb/auth_test.py
index d1b0625..c410562 100644
--- a/pygitweb/auth_test.py
+++ b/pygitweb/auth_test.py
@@ -9,7 +9,7 @@ from fastapi.testclient import TestClient
 
 from pygitweb.auth import ACCESS_TOKEN_COOKIE_NAME
 from pygitweb.auth_config import AuthConfig, LocalUser
-from pygitweb.auth_oauth import OAUTH_STATE_COOKIE_NAME, _seal_oauth_state
+from pygitweb.auth_oauth import OAUTH_STATE_COOKIE_NAME, _seal_oauth_state, is_oauth_email_allowed
 from pygitweb.config import settings
 from pygitweb.conftest import clear_client_cookies
 from pygitweb.gravatar import gravatar_url
@@ -217,6 +217,21 @@ def test_local_login_stores_session_audit_fields(client: TestClient, local_auth_
 	assert rec.email is None
 
 
+def test_oauth_allowed_emails_glob() -> None:
+	cfg = AuthConfig(auth_mode="oauth", oauth_allowed_emails=["*@example.com", "guest@corp.org"])
+	assert is_oauth_email_allowed(cfg, "alice@example.com")
+	assert is_oauth_email_allowed(cfg, "Alice@Example.COM")
+	assert is_oauth_email_allowed(cfg, "guest@corp.org")
+	assert not is_oauth_email_allowed(cfg, "alice@other.org")
+	assert not is_oauth_email_allowed(cfg, None)
+
+
+def test_oauth_allowed_emails_exact_match() -> None:
+	cfg = AuthConfig(auth_mode="oauth", oauth_allowed_emails=["only@example.com"])
+	assert is_oauth_email_allowed(cfg, "only@example.com")
+	assert not is_oauth_email_allowed(cfg, "other@example.com")
+
+
 _OAUTH_CFG = AuthConfig(
 	auth_mode="oauth",
 	oauth_provider="google",
