Invalid oauth RefreshToken makes user become inactive when syncing users from oauth provider #14611

Open
opened 2025-11-02 11:17:30 -06:00 by GiteaMirror · 20 comments
Owner

Originally created by @imhun on GitHub (Jun 17, 2025).

Description

oauth login: keycloak

Problem

git pull repo use app token,when oauth login session expired ,git pull error message:
/data.git/info/refs not valid: could not determine hash algorithm; is this a git repository?

gitea home page display:
Your account is prohibited from signing in, please contact your site administrator.

gitea service log message:

2025/06/17 01:54:10 HTTPRequest [I] router: completed GET /bytefabric/data.git/info/refs?service=git-upload-pack for 100.127.131.23:22476, 401 Unauthorized in 35.6ms @ repo/githttp.go:511(repo.GetInfoRefs)

2025-06-17T09:54:10.398305175+08:00 2025/06/17 01:54:10 routers/web/web.go:148:init.verifyAuthWithOptions.1() [I] Failed authentication attempt for xxx from 100.127.131.23:22476

It works properly if we log in again through the oauth login.

Expected

I understand that if git pull uses the app token, it should be able to function normally as long as the token is not expired, and there should be no need for the user to log in again.

Gitea Version

1.37-1.24

Can you reproduce the bug on the Gitea demo site?

No

Log Gist

No response

Screenshots

No response

Git Version

2.49.0

Operating System

k8s on linux arm64

How are you running Gitea?

k8s 1.32,gitea helm charts:12.0.0

Database

PostgreSQL

Originally created by @imhun on GitHub (Jun 17, 2025). ### Description oauth login: keycloak ### Problem git pull repo use app token,when oauth login session expired ,git pull error message: `/data.git/info/refs not valid: could not determine hash algorithm; is this a git repository?` gitea home page display: `Your account is prohibited from signing in, please contact your site administrator.` gitea service log message: ``` 2025/06/17 01:54:10 HTTPRequest [I] router: completed GET /bytefabric/data.git/info/refs?service=git-upload-pack for 100.127.131.23:22476, 401 Unauthorized in 35.6ms @ repo/githttp.go:511(repo.GetInfoRefs) 2025-06-17T09:54:10.398305175+08:00 2025/06/17 01:54:10 routers/web/web.go:148:init.verifyAuthWithOptions.1() [I] Failed authentication attempt for xxx from 100.127.131.23:22476 ``` It works properly if we log in again through the oauth login. ### Expected I understand that if git pull uses the app token, it should be able to function normally as long as the token is not expired, and there should be no need for the user to log in again. ### Gitea Version 1.37-1.24 ### Can you reproduce the bug on the Gitea demo site? No ### Log Gist _No response_ ### Screenshots _No response_ ### Git Version 2.49.0 ### Operating System k8s on linux arm64 ### How are you running Gitea? k8s 1.32,gitea helm charts:12.0.0 ### Database PostgreSQL
GiteaMirror added the type/bugissue/workaround labels 2025-11-02 11:17:30 -06:00
Author
Owner

@lunny commented on GitHub (Jun 17, 2025):

What did you mean app token? Is that access token or oauth2 token?

@lunny commented on GitHub (Jun 17, 2025): What did you mean `app token`? Is that access token or oauth2 token?
Author
Owner

@imhun commented on GitHub (Jun 17, 2025):

The applications token
Image

@imhun commented on GitHub (Jun 17, 2025): The applications token <img width="1610" alt="Image" src="https://github.com/user-attachments/assets/38229ba9-b692-4659-bc4b-220307872b9b" />
Author
Owner

@imhun commented on GitHub (Jun 17, 2025):

When using applications tokens to access package,for example, pulling the docker image, the same problem will occur.

@imhun commented on GitHub (Jun 17, 2025): When using applications tokens to access package,for example, pulling the docker image, the same problem will occur.
Author
Owner

@wxiaoguang commented on GitHub (Jun 17, 2025):

What's your app.ini config?

Especially:

  • REGISTER_EMAIL_CONFIRM
  • REGISTER_MANUAL_CONFIRM
@wxiaoguang commented on GitHub (Jun 17, 2025): What's your `app.ini` config? Especially: * REGISTER_EMAIL_CONFIRM * REGISTER_MANUAL_CONFIRM
Author
Owner

@wxiaoguang commented on GitHub (Jun 17, 2025):

gitea home page display: Your account is prohibited from signing in, please contact your site administrator.

This message should only be shown when the user's is_active=false or prohibit_login=true

Could you check your database about the user details when the problem occurs? If the values are not expected, and need to figure out why it becomes true.

@wxiaoguang commented on GitHub (Jun 17, 2025): > gitea home page display: `Your account is prohibited from signing in, please contact your site administrator.` This message should only be shown when the user's `is_active=false` or `prohibit_login=true` Could you check your database about the user details when the problem occurs? If the values are not expected, and need to figure out why it becomes true.
Author
Owner

@imhun commented on GitHub (Jun 18, 2025):

What's your app.ini config?

Especially:

  • REGISTER_EMAIL_CONFIRM
  • REGISTER_MANUAL_CONFIRM

Without this config
Image

when session expired,the user's is_active=f
Image

when login again,the is_active=t
Image

It seems to change dynamically along with the login status.

@imhun commented on GitHub (Jun 18, 2025): > What's your `app.ini` config? > > Especially: > > * REGISTER_EMAIL_CONFIRM > * REGISTER_MANUAL_CONFIRM Without this config <img width="561" alt="Image" src="https://github.com/user-attachments/assets/52eb4598-eabd-4f39-beed-420b7046d73c" /> when session expired,the user's is_active=f <img width="771" alt="Image" src="https://github.com/user-attachments/assets/cf8f780d-7d90-4dd0-88dc-737ba0499556" /> when login again,the is_active=t <img width="682" alt="Image" src="https://github.com/user-attachments/assets/f741e2c2-e2df-4b75-b539-52b23d88ea89" /> It seems to change dynamically along with the login status.
Author
Owner

@wxiaoguang commented on GitHub (Jun 18, 2025):

It seems to change dynamically along with the login status.

Can you see some strange logs? For example, something like log.Info("SyncExternalUsers[%s] disabling user %d", source.AuthSource.Name, user.ID)?

@wxiaoguang commented on GitHub (Jun 18, 2025): > It seems to change dynamically along with the login status. Can you see some strange logs? For example, something like `log.Info("SyncExternalUsers[%s] disabling user %d", source.AuthSource.Name, user.ID)`?
Author
Owner

@imhun commented on GitHub (Jun 18, 2025):

It seems to be so.
Image

@imhun commented on GitHub (Jun 18, 2025): It seems to be so. <img width="1183" alt="Image" src="https://github.com/user-attachments/assets/9de62bb4-2b45-4f0c-aa86-ff3437f6cd63" />
Author
Owner

@wxiaoguang commented on GitHub (Jun 18, 2025):

The related logic is here. TBH I don't have more ideas about it at the moment. Just FYI.

Image

@wxiaoguang commented on GitHub (Jun 18, 2025): The related logic is here. TBH I don't have more ideas about it at the moment. Just FYI. ![Image](https://github.com/user-attachments/assets/e6ac6584-e4ee-47c6-a85b-de05120663d5)
Author
Owner

@wxiaoguang commented on GitHub (Jun 18, 2025):

I think the problem is related to your SSO's RefreshToken behavior. For example: issuing/expiring/revoking.

@wxiaoguang commented on GitHub (Jun 18, 2025): I think the problem is related to your SSO's RefreshToken behavior. For example: issuing/expiring/revoking.
Author
Owner

@imhun commented on GitHub (Jun 18, 2025):

Should we disable the user when their session expires? Disabling the user will also affect the use of the app token, which feels a bit unreasonable.

@imhun commented on GitHub (Jun 18, 2025): Should we disable the user when their session expires? Disabling the user will also affect the use of the app token, which feels a bit unreasonable.
Author
Owner

@wxiaoguang commented on GitHub (Jun 18, 2025):

Should we disable the user when their session expires?

I don't have more ideas about this problem at the moment. And I don't know how your system works (especially the "RefreshToken" part).

Maybe you could try to debug it, and/or provide a reproducible setup with detailed steps then maybe when someone has time they could also take a look.

Disabling the user will also affect the use of the app token, which feels a bit unreasonable.

It is reasonable and correctly designed. The disabled users should have no access to the system.

@wxiaoguang commented on GitHub (Jun 18, 2025): > Should we disable the user when their session expires? I don't have more ideas about this problem at the moment. And I don't know how your system works (especially the "RefreshToken" part). Maybe you could try to debug it, and/or provide a reproducible setup with detailed steps then maybe when someone has time they could also take a look. > Disabling the user will also affect the use of the app token, which feels a bit unreasonable. It is reasonable and correctly designed. The disabled users should have no access to the system.
Author
Owner

@imhun commented on GitHub (Jun 23, 2025):

The default maximum session duration for gitea is one day,configured by parameter SESSION_LIFE_TIME.
The maximum session time for my sso is also one day.
It is possible that the sso session has expired, but the gitea session has not expired. This is why the problem occurred.
I set the maximum session time for sso to be longer than one day, say two days, and then this problem was resolved.

@imhun commented on GitHub (Jun 23, 2025): The default maximum session duration for gitea is one day,configured by parameter SESSION_LIFE_TIME. The maximum session time for my sso is also one day. It is possible that the sso session has expired, but the gitea session has not expired. This is why the problem occurred. I set the maximum session time for sso to be longer than one day, say two days, and then this problem was resolved.
Author
Owner

@wxiaoguang commented on GitHub (Jun 24, 2025):

IIRC every mid-night (by default) Gitea will use the RefreshToken to sync the users from oauth provider to its user table.

So if the RefreshToken becomes invalid, the related user will be marked as inactive.

@wxiaoguang commented on GitHub (Jun 24, 2025): IIRC every mid-night (by default) Gitea will use the RefreshToken to sync the users from oauth provider to its user table. So if the RefreshToken becomes invalid, the related user will be marked as inactive.
Author
Owner

@jon-nfc commented on GitHub (Jun 30, 2025):

I can also report the same problem. funnily enough, clicking signout and signing back in fixes the problem.

of note, reading through the questions it's clear there is a lack of understanding of the SSO process works that may possibly have led to the design decision of deactivating a user all because the OAUTH/OIDC flow was followed in accordance with how it works.

I think the problem is related to your SSO's RefreshToken behavior. For example: issuing/expiring/revoking.

@wxiaoguang, how so? if the refresh token has expired that means it cant be used to obtain a new id token to authenticate, Gitea should be returning HTTP/401 for this not HTTP/200 like it is now.

Should we disable the user when their session expires? Disabling the user will also affect the use of the app token, which feels a bit unreasonable.

@imhun, No you should not disable the user when the session expires. The normal process for this is to return HTTP/401 so they are forced to re-authenticate again. Disabling a user because their session has expired is ubsurd. Gitea knows nothing about the user until the user passes their token to gitea.

The normal flow for OAUTH (OIDC) is that when a user logs in they obtain their access token (which contains the refresh token), the former generally expires first with the refresh token set at a later datetime. other token also obtained if requested are the ID and user info tokens.

  • Now, if user x has a valid token nothing changes, they are good to go
  • if the users access/id token are expired and their refresh token has not, they use the refresh token to re-authenticate (read get new id/access token)
  • if the refresh token has expired, the system relying on this information for either or both of authentication and/or authorization must deny access and then should return HTTP/401 (Not authenticated), which in turn the app (gitea in this case) should redirect the user to the login page.

The idea behind the id/access token expiring is that if it's intercepted it is short lived so harm in reduced to the period of it's validity. As it's inconvenient for a user to re-login every 5 mins, this is where the refresh token comes in. The refresh token if still valid (should be, as it's expiry is normally much longer that id/access token), the user is re-authenticated.

If the refresh token has expired, access must be denied then the user must re-authenticate period. why deactivate the account? unless the user is passing an invalid token, which should trigger the re-login process. Gitea or any other app that uses this SSO method, has no way of knowing if the user is deactivated (if the sso provider deactivates the user, they cant get a token to even attempt to log in to gitea). You cant sync users from/to an OAUTH/OIDC provider. all gitea knows is if the signed token is valid (checks with sso provider) outside of that, how ever the token has been configured for use by the admin (re: claims etc) is the scope of giteas knowledge. marking a user as not allowed to login is absured especially since that's how OAUTH/OIDC work.

@jon-nfc commented on GitHub (Jun 30, 2025): I can also report the same problem. funnily enough, clicking signout and signing back in fixes the problem. of note, reading through the questions it's clear there is a lack of understanding of the SSO process works that may possibly have led to the design decision of deactivating a user all because the OAUTH/OIDC flow was followed in accordance with how it works. > I think the problem is related to your SSO's RefreshToken behavior. For example: issuing/expiring/revoking. @wxiaoguang, how so? if the refresh token has expired that means it cant be used to obtain a new id token to authenticate, Gitea should be returning HTTP/401 for this not HTTP/200 like it is now. > Should we disable the user when their session expires? Disabling the user will also affect the use of the app token, which feels a bit unreasonable. @imhun, No you should not disable the user when the session expires. The normal process for this is to return HTTP/401 so they are forced to re-authenticate again. Disabling a user because their session has expired is ubsurd. Gitea knows nothing about the user until the user passes their token to gitea. The normal flow for OAUTH (OIDC) is that when a user logs in they obtain their access token (which contains the refresh token), the former generally expires first with the refresh token set at a later datetime. other token also obtained if requested are the ID and user info tokens. - Now, if user x has a valid token nothing changes, they are good to go - if the users access/id token are expired and their refresh token has not, they use the refresh token to re-authenticate (read get new id/access token) - if the refresh token has expired, the system relying on this information for either or both of authentication and/or authorization must deny access and then should return HTTP/401 (Not authenticated), which in turn the app (gitea in this case) should redirect the user to the login page. The idea behind the id/access token expiring is that if it's intercepted it is short lived so harm in reduced to the period of it's validity. As it's inconvenient for a user to re-login every 5 mins, this is where the refresh token comes in. The refresh token if still valid (should be, as it's expiry is normally much longer that id/access token), the user is re-authenticated. If the refresh token has expired, access must be denied then the user must re-authenticate period. why deactivate the account? unless the user is passing an invalid token, which should trigger the re-login process. Gitea or any other app that uses this SSO method, has no way of knowing if the user is deactivated (if the sso provider deactivates the user, they cant get a token to even attempt to log in to gitea). You cant sync users from/to an OAUTH/OIDC provider. all gitea knows is if the signed token is valid (checks with sso provider) outside of that, how ever the token has been configured for use by the admin (re: claims etc) is the scope of giteas knowledge. marking a user as not allowed to login is absured especially since that's how OAUTH/OIDC work.
Author
Owner

@jon-nfc commented on GitHub (Jun 30, 2025):

IIRC every mid-night (by default) Gitea will use the RefreshToken to sync the users from oauth provider to its user table.

So if the RefreshToken becomes invalid, the related user will be marked as inactive.

on a further note, this should not be occurring, the purpose of the tokens is that ONLY the user keeps a copy. This is a fundamental principle of the design of the OAUTH/OIDC protocol. OAUTH/OIDC is called stateless for that very reason. The decision to store users tokens is a serious security violation that needs to be addressed. Why? compromise one gitea server and you have essentially given the attacker the password of every OAUTH/OIDC user. In turn this security violation now affects every other service the user has access to, that the SSO provider is used to authenticate with. The impact area of this decision to store this token not only effects gitea but possibly thousands of other systems.

This needs to be fixed, how? dont store tokens you are not supposed to, why? simply put they are the equivalent of a password (technically better than, but I digress) and not just any password, one that can be used continually and are very difficult to revoke without significant impact

I implore the dev team to prioritize this as an immediate security fix.

Giteas only use of the token is to confirm validity for access. If this fails, the user MUST be DENIED access and forced to re-authenticate.

@jon-nfc commented on GitHub (Jun 30, 2025): > IIRC every mid-night (by default) Gitea will use the RefreshToken to sync the users from oauth provider to its user table. > > So if the RefreshToken becomes invalid, the related user will be marked as inactive. on a further note, this should not be occurring, the purpose of the tokens is that ONLY the user keeps a copy. This is a fundamental principle of the design of the OAUTH/OIDC protocol. OAUTH/OIDC is called stateless for that very reason. The decision to store users tokens is a serious security violation that needs to be addressed. Why? compromise one gitea server and you have essentially given the attacker the password of every OAUTH/OIDC user. In turn this security violation now affects every other service the user has access to, that the SSO provider is used to authenticate with. The impact area of this decision to store this token not only effects gitea but possibly thousands of other systems. **This needs to be fixed, how? dont store tokens you are not supposed to, why? simply put they are the equivalent of a password (technically better than, but I digress) and not just any password, one that can be used continually and are very difficult to revoke without significant impact** **I implore the dev team to prioritize this as an immediate security fix.** Giteas only use of the token is to confirm validity for access. If this fails, the user MUST be DENIED access and forced to re-authenticate.
Author
Owner

@jon-nfc commented on GitHub (Jun 30, 2025):

To further assist.... sorry for the spam.....

here's a docs with references for assistance ,thanks to chatGPT


OAuth 2.0 and OpenID Connect (OIDC) are modern authentication and authorization protocols widely used for secure, delegated access. Let's break this down in detail, with correct official references, and explain how and why statelessness and token handling are critical concepts in their security models.


📉 1. What is OAuth 2.0 and OIDC?

🔐 OAuth 2.0

OAuth 2.0 is a protocol that allows a user to grant limited access to their resources (usually on one site) to another site, without exposing their credentials.

🗝 OpenID Connect (OIDC)

OIDC is an identity layer on top of OAuth 2.0. It adds authentication (who the user is) in addition to authorization (what they can do).

OIDC introduces the ID Token, a JWT that contains user identity claims (e.g., sub, email, etc.).


🔄 2. How the OAuth/OIDC Workflow Works (Statelessly)

Example: Authorization Code Flow (with PKCE)

This is the most common flow for web/mobile apps.

Step-by-step:

  1. Client (e.g., web app) redirects the user to the Authorization Server.

  2. User logs in and grants access.

  3. Authorization Server redirects back to the client with a code.

  4. Client exchanges the code (plus PKCE secret) for:

    • an access token (used to access APIs)
    • an ID token (OIDC only; contains user identity)
  5. The client stores these in browser memory (not persistent storage).

This whole flow is stateless on the client side because:

  • No user session is stored on the server.
  • The tokens themselves carry the session context.
  • If the token is gone (e.g., browser closed), the user is logged out.

📘 PKCE Spec (RFC 7636):
https://datatracker.ietf.org/doc/html/rfc7636


🚫 3. Why Only the User (Client) Should Store Tokens

Tokens (especially access tokens and refresh tokens) are bearer credentialsanyone who has them can impersonate the user.

🧠 Design Principle: Statelessness

  • The backend (API/resource server) should verify the token each time.
  • It doesn’t need to store any session or token itself.
  • All claims or state is encoded in the JWT (or verified with the authorization server via introspection).

Where to Store:

  • Store access tokens only in memory (e.g., browser memory, app memory).
  • Never store in localStorage or sessionStorage due to XSS risk.
  • Use Secure HttpOnly cookies only in trusted environments with CSRF protection.

4. Why Other Systems Should NOT Store Tokens

Imagine a system (e.g., API gateway, third-party microservice) storing tokens in a DB or file system:

🚨 Risks:

  • Security Breach: If storage is compromised, all tokens are valid and can be replayed.
  • Replay Attacks: Tokens can be reused if intercepted/stored.
  • Revocation Difficulties: You now need a way to invalidate old tokens across systems.
  • Session State Complexity: You're adding back session management that OAuth/OIDC avoids by design.

🔒 Instead:

  • Downstream systems should validate the access token:

    • By verifying the JWT signature (using the provider’s public keys: OIDC Discovery URL).
    • Or via OAuth token introspection endpoint.

📘 Token Introspection Spec (RFC 7662):
https://datatracker.ietf.org/doc/html/rfc7662

📘 OIDC Discovery (Well-Known URL):
https://openid.net/specs/openid-connect-discovery-1_0.html
Example:
https://accounts.google.com/.well-known/openid-configuration


🧪 5. Example: Good vs Bad Token Handling

Good:

// Token stored in-memory
let accessToken = await getAccessToken();
let userInfo = await fetch("/userinfo", {
  headers: { Authorization: `Bearer ${accessToken}` }
});

Bad:

# Storing access tokens in a database
db.tokens.insert({"user": "alice", "token": "eyJhbGciOiJI..."})
# Highly risky if DB is leaked

📦 Summary

Principle Best Practice
Statelessness Don’t store user session server-side
Token storage Only client stores tokens, in memory
API/backend handling Validate token each request (JWT verify or introspect)
Token persistence Avoid writing tokens to disk/DB
Revocation Use short-lived access tokens + refresh token rotation

🔗 Authoritative References


If you want working example code (e.g., Python Flask + Auth0, or Node.js + OIDC), I can provide those too.

@jon-nfc commented on GitHub (Jun 30, 2025): To further assist.... sorry for the spam..... here's a docs with references for assistance ,thanks to chatGPT --- OAuth 2.0 and OpenID Connect (OIDC) are modern authentication and authorization protocols widely used for secure, delegated access. Let's break this down in detail, with correct official references, and explain how and why **statelessness** and **token handling** are critical concepts in their security models. --- ## 📉 1. What is OAuth 2.0 and OIDC? ### 🔐 OAuth 2.0 OAuth 2.0 is a protocol that allows a user to grant limited access to their resources (usually on one site) to another site, **without exposing their credentials**. * **RFC 6749** (OAuth 2.0 Framework): [https://datatracker.ietf.org/doc/html/rfc6749](https://datatracker.ietf.org/doc/html/rfc6749) ### 🗝 OpenID Connect (OIDC) OIDC is an identity layer on top of OAuth 2.0. It adds authentication (who the user is) in addition to authorization (what they can do). * **OIDC Core Spec**: [https://openid.net/specs/openid-connect-core-1\_0.html](https://openid.net/specs/openid-connect-core-1_0.html) OIDC introduces the **ID Token**, a JWT that contains user identity claims (e.g., `sub`, `email`, etc.). --- ## 🔄 2. How the OAuth/OIDC Workflow Works (Statelessly) ### Example: Authorization Code Flow (with PKCE) This is the most common flow for web/mobile apps. #### Step-by-step: 1. **Client (e.g., web app)** redirects the user to the **Authorization Server**. 2. **User logs in** and grants access. 3. **Authorization Server** redirects back to the client with a **code**. 4. **Client exchanges** the code (plus PKCE secret) for: * an **access token** (used to access APIs) * an **ID token** (OIDC only; contains user identity) 5. The client stores these in **browser memory** (not persistent storage). ✅ This whole flow is **stateless** on the client side because: * No user session is stored on the server. * The **tokens themselves carry the session context**. * If the token is gone (e.g., browser closed), the user is logged out. 📘 PKCE Spec (RFC 7636): [https://datatracker.ietf.org/doc/html/rfc7636](https://datatracker.ietf.org/doc/html/rfc7636) --- ## 🚫 3. Why Only the User (Client) Should Store Tokens Tokens (especially access tokens and refresh tokens) are **bearer credentials** — **anyone who has them can impersonate the user**. ### 🧠 Design Principle: **Statelessness** * The backend (API/resource server) should **verify the token** each time. * It doesn’t need to store any session or token itself. * All claims or state is encoded in the **JWT** (or verified with the authorization server via introspection). ### ✅ Where to Store: * Store access tokens **only in memory** (e.g., browser memory, app memory). * Never store in localStorage or sessionStorage due to XSS risk. * Use **Secure HttpOnly cookies** only in trusted environments with CSRF protection. --- ## ❌ 4. Why Other Systems Should NOT Store Tokens Imagine a system (e.g., API gateway, third-party microservice) storing tokens in a DB or file system: ### 🚨 Risks: * **Security Breach**: If storage is compromised, all tokens are valid and can be replayed. * **Replay Attacks**: Tokens can be reused if intercepted/stored. * **Revocation Difficulties**: You now need a way to invalidate old tokens across systems. * **Session State Complexity**: You're adding back session management that OAuth/OIDC avoids by design. ### 🔒 Instead: * Downstream systems should validate the access token: * By verifying the **JWT signature** (using the provider’s public keys: OIDC Discovery URL). * Or via **OAuth token introspection** endpoint. 📘 Token Introspection Spec (RFC 7662): [https://datatracker.ietf.org/doc/html/rfc7662](https://datatracker.ietf.org/doc/html/rfc7662) 📘 OIDC Discovery (Well-Known URL): [https://openid.net/specs/openid-connect-discovery-1\_0.html](https://openid.net/specs/openid-connect-discovery-1_0.html) Example: `https://accounts.google.com/.well-known/openid-configuration` --- ## 🧪 5. Example: Good vs Bad Token Handling ### ✅ Good: ```javascript // Token stored in-memory let accessToken = await getAccessToken(); let userInfo = await fetch("/userinfo", { headers: { Authorization: `Bearer ${accessToken}` } }); ``` ### ❌ Bad: ```python # Storing access tokens in a database db.tokens.insert({"user": "alice", "token": "eyJhbGciOiJI..."}) # Highly risky if DB is leaked ``` --- ## 📦 Summary | Principle | Best Practice | | -------------------- | ------------------------------------------------------ | | Statelessness | Don’t store user session server-side | | Token storage | Only client stores tokens, in memory | | API/backend handling | Validate token each request (JWT verify or introspect) | | Token persistence | Avoid writing tokens to disk/DB | | Revocation | Use short-lived access tokens + refresh token rotation | --- ## 🔗 Authoritative References * [OAuth 2.0 Framework (RFC 6749)](https://datatracker.ietf.org/doc/html/rfc6749) * [OAuth 2.0 Bearer Token Usage (RFC 6750)](https://datatracker.ietf.org/doc/html/rfc6750) * [OAuth 2.0 PKCE (RFC 7636)](https://datatracker.ietf.org/doc/html/rfc7636) * [OIDC Core Specification](https://openid.net/specs/openid-connect-core-1_0.html) * [OIDC Discovery Spec](https://openid.net/specs/openid-connect-discovery-1_0.html) * [OAuth Token Introspection (RFC 7662)](https://datatracker.ietf.org/doc/html/rfc7662) --- If you want working example code (e.g., Python Flask + Auth0, or Node.js + OIDC), I can provide those too.
Author
Owner

@wxiaoguang commented on GitHub (Jun 30, 2025):

There are indeed many design problems in the oauth modules. Sometimes the problem is not that easy to fix as it looks.

Let’s not discuss whether this design is right or wrong at the moment, just take a look at the current behavior: it deactivates the user whose RefreshToken becomes invalid. If we change this behavior (not store it), then some site admins would complain that: "the user is still able to access the code when they has been disabled in my oauth SSO".

And FYI, there are some PRs like "Support OIDC RP-initiated logout #30072", in case anyone is interested to help.


Disclaimer: I didn't use oauth on my instance, I just happened to see this problem and tried to understand more about it. I am not oauth2 expert and I can't promise I have time on it, so feel free to propose proper fixes & PRs, thank you very much!

@wxiaoguang commented on GitHub (Jun 30, 2025): There are indeed many design problems in the oauth modules. Sometimes the problem is not that easy to fix as it looks. Let’s not discuss whether this design is right or wrong at the moment, just take a look at the current behavior: it deactivates the user whose RefreshToken becomes invalid. If we change this behavior (not store it), then some site admins would complain that: "the user is still able to access the code when they has been disabled in my oauth SSO". And FYI, there are some PRs like "Support OIDC RP-initiated logout #30072", in case anyone is interested to help. ----- Disclaimer: I didn't use oauth on my instance, I just happened to see this problem and tried to understand more about it. I am not oauth2 expert and I can't promise I have time on it, so feel free to propose proper fixes & PRs, thank you very much!
Author
Owner

@jon-nfc commented on GitHub (Jun 30, 2025):

There are indeed many design problems in the oauth modules. Sometimes the problem is not that easy to fix as it looks.

I do understand that pain!!

Let’s not discuss whether this design is right or wrong at the moment, just take a look at the current behavior: it deactivates the user whose RefreshToken becomes invalid.

agreed, however addressing the security problem removes the current behaviour.

If we change this behavior (not store it), then some site admins would complain that: "the user is still able to access the code when they has been disabled in my oauth SSO".

That is clearly a lack of understating of the protocol in the first place and not a reason to introduce a significant security problem. OAUTH/OIDC exists for the sole purpose of not knowing anything about the user except what they provide in a valid token. the issue of a disabled user on the sso side still accessing code is because the tokens validity period was not thought out enough. revoking system access in this sso systems case is a significant challenge. I do agree though that gitea does need to have the "user is deactivated box" as this allows for admins to prevent access immediately. (and for automations to do if they detect access revoking from sso server)

And FYI, there are some PRs like "Support OIDC RP-initiated logout https://github.com/go-gitea/gitea/pull/30072", in case anyone is interested to help.

👍

Disclaimer: I didn't use oauth on my instance, I just happened to see this problem and tried to understand more about it. I am not oauth2 expert and I can't promise I have time on it, so feel free to propose proper fixes & PRs, thank you very much!

An avid opensource user, that I can appreciate. I cant write in go so cant help there (well that's a lie, I refuse to learn another language especially when it will be of no use). My suggestion and the only thing that needs to be done to address this problem is the remove the storing of the tokens period. when that occurs the issue reported by OP goes away.

@jon-nfc commented on GitHub (Jun 30, 2025): >There are indeed many design problems in the oauth modules. Sometimes the problem is not that easy to fix as it looks. I do understand that pain!! > Let’s not discuss whether this design is right or wrong at the moment, just take a look at the current behavior: it deactivates the user whose RefreshToken becomes invalid. agreed, however addressing the security problem removes the current behaviour. > If we change this behavior (not store it), then some site admins would complain that: "the user is still able to access the code when they has been disabled in my oauth SSO". That is clearly a lack of understating of the protocol in the first place and not a reason to introduce a significant security problem. OAUTH/OIDC exists for the sole purpose of not knowing anything about the user except what they provide in a **valid** token. the issue of a disabled user on the sso side still accessing code is because the tokens validity period was not thought out enough. revoking system access in this sso systems case is a significant challenge. I do agree though that gitea does need to have the "user is deactivated box" as this allows for admins to prevent access immediately. (and for automations to do if they detect access revoking from sso server) > And FYI, there are some PRs like "Support OIDC RP-initiated logout https://github.com/go-gitea/gitea/pull/30072", in case anyone is interested to help. 👍 > Disclaimer: I didn't use oauth on my instance, I just happened to see this problem and tried to understand more about it. I am not oauth2 expert and I can't promise I have time on it, so feel free to propose proper fixes & PRs, thank you very much! An avid opensource user, that I can appreciate. I cant write in go so cant help there (well that's a lie, I refuse to learn another language especially when it will be of no use). My suggestion and the only thing that needs to be done to address this problem is the remove the storing of the tokens period. when that occurs the issue reported by OP goes away.
Author
Owner

@neruthes commented on GitHub (Sep 13, 2025):

What I see is slightly different but I believe that we are facing the same fundamental problem in design decisions. I visited root admin dashboard and I see my account was deactivated unexpectedly.

Cloning into 'xxxxxxx'...
error:
error: Your account is disabled.
error:
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

I would like to mention a use case: a user account that commits and pushes code every day and visits web on every Friday. Deactivating the account on Sunday (due to refresh token, etc) is clearly absurd.

I would further argue that any automatic account deactivation is absurd. If necessary, site admin may have a button "Sync user-deactivations from SSO upstream" (choosing a particular upstream in params). At least I need an option to make sure that my Gitea instance never deactivates accounts unless I do it manually.

@neruthes commented on GitHub (Sep 13, 2025): What I see is slightly different but I believe that we are facing the same fundamental problem in design decisions. I visited root admin dashboard and I see my account was deactivated unexpectedly. ``` Cloning into 'xxxxxxx'... error: error: Your account is disabled. error: fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists. ``` I would like to mention a use case: a user account that commits and pushes code every day and visits web on every Friday. Deactivating the account on Sunday (due to refresh token, etc) is clearly absurd. I would further argue that any automatic account deactivation is absurd. If necessary, site admin may have a button "Sync user-deactivations from SSO upstream" (choosing a particular upstream in params). At least I need an option to make sure that my Gitea instance never deactivates accounts unless I do it manually.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/gitea#14611