EntraID SignIn Error: oauth2: error validating JWT token: user info JWT token is expired

Hi,

I’m new to Gitea and I’m really struggling to setup an instance on Windows with LetsEncrypt SSL certificate and Entra ID as authentication provider.

By now I have a running instance on an English Windows Server 2025 with an IIS reverse proxy and a Let’s Encrypt Certificate. The server is hardened with most of the CIS recommendations.

I tried to configure Entra ID Sign-In with this (which is exactly what I want) but I get the error:

„There was an error processing the authorization request: oauth2: error validating JWT token: user info JWT token is expired. If this error persists, please contact the site administrator.“

This is my config (domain name redacted):

Gitea located in „C:\program files\gitea\“

Version 1.27

App.ini

APP_NAME = Gitea

RUN_USER = Gitea$

WORK_PATH = C:\Program Files\Gitea

RUN_MODE = prod

[database]

DB_TYPE = sqlite3

HOST = 127.0.0.1:3306

NAME = gitea

USER = gitea

PASSWD =

SCHEMA =

SSL_MODE = disable

PATH = C:\Program Files\Gitea\data\gitea.db

LOG_SQL = false

[repository]

ROOT = C:/Program Files/Gitea/data/gitea-repositories

[server]

SSH_DOMAIN = localhost

DOMAIN = localhost

HTTP_PORT = 3000

ROOT_URL = https://gitea.domain.de/

APP_DATA_PATH = C:\Program Files\Gitea\data

DISABLE_SSH = false

SSH_PORT = 22

LFS_START_SERVER = true

LFS_JWT_SECRET =

OFFLINE_MODE = true

[lfs]

PATH = C:/Program Files/Gitea/data/lfs

[mailer]

ENABLED = false

[service]

REGISTER_EMAIL_CONFIRM = false

ENABLE_NOTIFY_MAIL = false

DISABLE_REGISTRATION = true

ALLOW_ONLY_EXTERNAL_REGISTRATION = true

ENABLE_CAPTCHA = false

REQUIRE_SIGNIN_VIEW = true

DEFAULT_KEEP_EMAIL_PRIVATE = false

DEFAULT_ALLOW_CREATE_ORGANIZATION = false

DEFAULT_ENABLE_TIMETRACKING = true

NO_REPLY_ADDRESS = noreply.localhost

[openid]

ENABLE_OPENID_SIGNIN = false

ENABLE_OPENID_SIGNUP = false

[cron.update_checker]

ENABLED = true

[session]

PROVIDER = file

[log]

MODE = file

LEVEL = debug

ROOT_PATH = C:/Program Files/Gitea/log

LOG_ROTATE = TRUE

DAILY_ROTATE = TRUE

MAX_DAYS = 7

[repository.pull-request]

DEFAULT_MERGE_STYLE = merge

[repository.signing]

DEFAULT_TRUST_MODEL = committer

[security]

INSTALL_LOCK = true

INTERNAL_TOKEN =

PASSWORD_HASH_ALGO = pbkdf2

; ;JWT_SECRET =

; ;ENABLE_AUTO_REGISTRATION = true

; ;ACCESS_TOKEN_EXPIRATION_TIME = 36000

[oauth2]

JWT_SECRET =

; ;ENABLE_AUTO_REGISTRATION = true

; ;USERNAME = prefered_username

; ;ACCOUNT_LINKING = auto

; ;OPENID_CONNECT_SCOPES = openid profile email

[time]

DEFAULT_UI_LOCATION = Europe/Berlin

IIS

IIS hast he following two options configured:

„system.webServer/proxy“: „preserveHostHeader“ = true

„system.webServer/proxy“: „reverseRewriteHostInResponseHeaders = false

Web.config

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.web>
        <httpRuntime requestPathInvalidCharacters="" />
    </system.web>
    <system.webServer>
        <security>
          <requestFiltering>
            <hiddenSegments>
              <clear />
            </hiddenSegments>
            <denyUrlSequences>
              <clear />
            </denyUrlSequences>
            <fileExtensions allowUnlisted="true">
              <clear />
            </fileExtensions>
          </requestFiltering>
        </security>
        <rewrite>
            <rules useOriginalURLEncoding="false">
                <rule name="ReverseProxyInboundRule1" stopProcessing="true">
                    <match url="(.*)" />
                    <action type="Rewrite" url="http://127.0.0.1:3000{UNENCODED_URL}" />
                    <serverVariables>
                        <set name="HTTP_X_ORIGINAL_ACCEPT_ENCODING" value="HTTP_ACCEPT_ENCODING" />
                        <set name="HTTP_ACCEPT_ENCODING" value="" />
                        <set name="HTTP_X_FORWARDED_PROTO" value="https" />			
                    </serverVariables>
                </rule>
            </rules>
            <outboundRules>
                <rule name="ReverseProxyOutboundRule1" preCondition="ResponseIsHtml1">
                    <!-- set the pattern correctly here - if you only want to accept http or https -->
                    <!-- change the pattern and the action value as appropriate -->
                    <match filterByTags="A, Form, Img" pattern="^http(s)?://127.0.0.1:3000/(.*)" />
                    <action type="Rewrite" value="https://gitea.domain.de/{R:2}" />
                </rule>
                <rule name="RestoreAcceptEncoding" preCondition="NeedsRestoringAcceptEncoding">
                    <match serverVariable="HTTP_ACCEPT_ENCODING" pattern="^(.*)" />
                    <action type="Rewrite" value="{HTTP_X_ORIGINAL_ACCEPT_ENCODING}" />
                </rule>
                <preConditions>
                    <preCondition name="ResponseIsHtml1">
                        <add input="{RESPONSE_CONTENT_TYPE}" pattern="^text/html" />
                    </preCondition>
                    <preCondition name="NeedsRestoringAcceptEncoding">
                        <add input="{HTTP_X_ORIGINAL_ACCEPT_ENCODING}" pattern=".+" />
                    </preCondition>
                </preConditions>
            </outboundRules>
        </rewrite>
        <urlCompression doDynamicCompression="true" />
        <!-- Map all extensions to the same MIME type, so all files can be
               downloaded. -->
        <staticContent>
          <clear />
          <mimeMap fileExtension="*" mimeType="application/octet-stream" />
        </staticContent>
    </system.webServer>
</configuration>

Azure App

API permissions granted.

Gitea Authentication Provider

I can see successful sign-ins in the Entra log.

I could not find anything on the net which helped me with this problem.

Any suggestions?

Thank you

Alex

The exact `oauth2: error validating JWT token: user info JWT token is expired` message is often caused by clock skew between the Gitea host, the reverse proxy, and Entra ID, especially when the token lifetime is short. I would first compare UTC time on the Gitea/IIS host with a trusted time source, then verify that the proxy preserves the original Host and forwards the expected HTTPS/X-Forwarded-* headers consistently. If the clocks match, capture a fresh token and compare its `exp`, `iat`, and issuer values with the OAuth provider configuration rather than changing token validation broadly. Do you see the same expiry error when accessing Gitea directly without the IIS proxy?

Hi,

thank you. The time seemed correct but it wasn’t as the time zone of the server was wrong.

Now I can authenticate but the account is neither automatically created, nor linked if I create the account automatically. If I link it manually it is not added to the organization :frowning:

My last attempt:

Any suggestions?

Alex