diff --git a/oauthenticator/gitlab.py b/oauthenticator/gitlab.py
index 860aa92..5afddb8 100644
--- a/oauthenticator/gitlab.py
+++ b/oauthenticator/gitlab.py
@@ -107,7 +107,7 @@ class GitLabOAuthenticator(OAuthenticator):
 
         username = resp_json["username"]
         user_id = resp_json["id"]
-        is_admin = resp_json["is_admin"]
+        is_admin = resp_json.get("is_admin", False)
 
         # Check if user is a member of any whitelisted organizations.
         # This check is performed here, as it requires `access_token`.

GitLab auth fails for non admin users
Recent changes in GitLab's API have removed the "is_admin" property from most API calls: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10846

Now it is only shown when the request comes from an admin user:
https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12211

This causes an error 500 for non-admin users, when "is_admin" is not found in the response.
diff --git a/oauthenticator/tests/test_gitlab.py b/oauthenticator/tests/test_gitlab.py
index 86d2b45..5163a49 100644
--- a/oauthenticator/tests/test_gitlab.py
+++ b/oauthenticator/tests/test_gitlab.py
@@ -16,11 +16,15 @@ from .mocks import setup_oauth_mock
 
 def user_model(username, id=1, is_admin=False):
     """Return a user model"""
-    return {
+    user = {
         'username': username,
         'id': id,
-        'is_admin': is_admin
     }
+    if is_admin:
+      # Some versions of the API do not return the is_admin property
+        # for non-admin users (See #115).
+        user['is_admin'] = True
+    return user
 
 @fixture
 def gitlab_client(client):
@@ -58,12 +62,10 @@ def test_group_whitelist(gitlab_client):
         'burns': ['blue', 'yellow'],
     })
 
-    def user_model(username, is_admin=False):
-        return {
-            'username': username,
-            'id': list(user_groups.keys()).index(username) + 1,
-            'is_admin': is_admin
-        }
+    def group_user_model(username, is_admin=False):
+        return user_model(username,
+                          list(user_groups.keys()).index(username) + 1,
+                          is_admin)
 
 
     member_regex = re.compile(r'/api/v3/groups/(.*)/members/(.*)')
@@ -116,30 +118,30 @@ def test_group_whitelist(gitlab_client):
 
         authenticator.gitlab_group_whitelist = ['blue']
 
-        handler = client.handler_for_user(user_model('caboose'))
+        handler = client.handler_for_user(group_user_model('caboose'))
         name = yield authenticator.authenticate(handler)
         assert name == 'caboose'
 
-        handler = client.handler_for_user(user_model('burns', is_admin=True))
+        handler = client.handler_for_user(group_user_model('burns', is_admin=True))
         name = yield authenticator.authenticate(handler)
         assert name == 'burns'
 
-        handler = client.handler_for_user(user_model('grif'))
+        handler = client.handler_for_user(group_user_model('grif'))
         name = yield authenticator.authenticate(handler)
         assert name is None
 
-        handler = client.handler_for_user(user_model('simmons', is_admin=True))
+        handler = client.handler_for_user(group_user_model('simmons', is_admin=True))
         name = yield authenticator.authenticate(handler)
         assert name is None
 
         # reverse it, just to be safe
         authenticator.gitlab_group_whitelist = ['red']
 
-        handler = client.handler_for_user(user_model('caboose'))
+        handler = client.handler_for_user(group_user_model('caboose'))
         name = yield authenticator.authenticate(handler)
         assert name is None
 
-        handler = client.handler_for_user(user_model('grif'))
+        handler = client.handler_for_user(group_user_model('grif'))
         name = yield authenticator.authenticate(handler)
         assert name == 'grif'
 

pip install -e .
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-tornado requests-mock flake8 pyjwt codecov
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alembic==1.15.2
annotated-types==0.7.0
arrow==1.3.0
async-generator==1.10
attrs==25.3.0
certifi==2025.1.31
certipy==0.2.2
cffi==1.17.1
charset-normalizer==3.4.1
codecov==2.1.13
coverage==7.8.0
cryptography==44.0.2
exceptiongroup==1.2.2
flake8==7.2.0
fqdn==1.5.1
greenlet==3.1.1
idna==3.10
importlib_metadata==8.6.1
iniconfig==2.1.0
isoduration==20.11.0
Jinja2==3.1.6
jsonpointer==3.0.0
jsonschema==4.23.0
jsonschema-specifications==2024.10.1
jupyter-events==0.12.0
jupyterhub==5.2.1
Mako==1.3.9
MarkupSafe==3.0.2
mccabe==0.7.0
-e git+https://github.com/jupyterhub/oauthenticator.git@ac730b276184640227c2ca999bfccfe7472fca85#egg=oauthenticator
oauthlib==3.2.2
packaging==24.2
pamela==1.2.0
pluggy==1.5.0
prometheus_client==0.21.1
pycodestyle==2.13.0
pycparser==2.22
pydantic==2.11.1
pydantic_core==2.33.0
pyflakes==3.3.2
PyJWT==2.10.1
pytest==8.3.5
pytest-cov==6.0.0
pytest-tornado==0.8.1
python-dateutil==2.9.0.post0
python-json-logger==3.3.0
PyYAML==6.0.2
referencing==0.36.2
requests==2.32.3
requests-mock==1.12.1
rfc3339-validator==0.1.4
rfc3986-validator==0.1.1
rpds-py==0.24.0
six==1.17.0
SQLAlchemy==2.0.40
tomli==2.2.1
tornado==6.4.2
traitlets==5.14.3
types-python-dateutil==2.9.0.20241206
typing-inspection==0.4.0
typing_extensions==4.13.0
uri-template==1.3.0
urllib3==2.3.0
webcolors==24.11.1
zipp==3.21.0

name: oauthenticator
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alembic==1.15.2
      - annotated-types==0.7.0
      - arrow==1.3.0
      - async-generator==1.10
      - attrs==25.3.0
      - certifi==2025.1.31
      - certipy==0.2.2
      - cffi==1.17.1
      - charset-normalizer==3.4.1
      - codecov==2.1.13
      - coverage==7.8.0
      - cryptography==44.0.2
      - exceptiongroup==1.2.2
      - flake8==7.2.0
      - fqdn==1.5.1
      - greenlet==3.1.1
      - idna==3.10
      - importlib-metadata==8.6.1
      - iniconfig==2.1.0
      - isoduration==20.11.0
      - jinja2==3.1.6
      - jsonpointer==3.0.0
      - jsonschema==4.23.0
      - jsonschema-specifications==2024.10.1
      - jupyter-events==0.12.0
      - jupyterhub==5.2.1
      - mako==1.3.9
      - markupsafe==3.0.2
      - mccabe==0.7.0
      - oauthlib==3.2.2
      - packaging==24.2
      - pamela==1.2.0
      - pluggy==1.5.0
      - prometheus-client==0.21.1
      - pycodestyle==2.13.0
      - pycparser==2.22
      - pydantic==2.11.1
      - pydantic-core==2.33.0
      - pyflakes==3.3.2
      - pyjwt==2.10.1
      - pytest==8.3.5
      - pytest-cov==6.0.0
      - pytest-tornado==0.8.1
      - python-dateutil==2.9.0.post0
      - python-json-logger==3.3.0
      - pyyaml==6.0.2
      - referencing==0.36.2
      - requests==2.32.3
      - requests-mock==1.12.1
      - rfc3339-validator==0.1.4
      - rfc3986-validator==0.1.1
      - rpds-py==0.24.0
      - six==1.17.0
      - sqlalchemy==2.0.40
      - tomli==2.2.1
      - tornado==6.4.2
      - traitlets==5.14.3
      - types-python-dateutil==2.9.0.20241206
      - typing-extensions==4.13.0
      - typing-inspection==0.4.0
      - uri-template==1.3.0
      - urllib3==2.3.0
      - webcolors==24.11.1
      - zipp==3.21.0
prefix: /opt/conda/envs/oauthenticator

oauthenticator/tests/test_gitlab.py::test_gitlab oauthenticator/tests/test_gitlab.py::test_group_whitelist
oauthenticator/gitlab.py
oauthenticator/gitlab.py