Feature Description
When a client asks for a scope set that differs from the user's existing OAuth2 grant, Gitea rejects the authorization instead of letting the user approve the change. The only way forward is for the user to manually revoke the application under Settings ▸ Applications ▸ Authorized OAuth2 Applications and authorize again.
In routers/web/auth/oauth2_provider.go (GrantApplicationOAuth, main around line 430):
} else if grant.Scope != form.Scope {
handleAuthorizeError(ctx, AuthorizeError{
State: form.State,
ErrorDescription: "a grant exists with different scope",
ErrorCode: ErrorCodeServerError,
}, form.RedirectURI)
return
}
For a public client (a mobile or desktop app), the consent page is always shown, so the user reads the permission list, presses Authorize, and then receives error=server_error&error_description=a+grant+exists+with+different+scope. From the user's point of view the app is broken, and every client has to ship documentation explaining a manual revocation step.
There is a second, quieter problem in the same file (AuthorizeOAuth, main around line 324):
if (app.ConfidentialClient || app.SkipSecondaryAuthorization) && grant != nil {
// issues an authorization code for the existing grant
For a confidential or trusted application with an existing grant, the requested scope is never compared to grant.Scope. The client receives a token carrying the previously granted scopes and no error at all — a silent scope downgrade. The token response does return scope (response.Scope = grant.Scope), so a careful client can detect it, but nothing tells the user that the newly requested permission was not granted.
The practical impact: any application that gains a feature needing an additional scope cannot ship it to its existing users without asking every one of them to revoke the authorization by hand. We hit this in a Gitea mobile client that added site-administration and package features, which need read:admin/write:admin and read:package/write:package.
How other providers behave
- GitHub (OAuth apps): an app can request additional scopes by sending the user through the authorize endpoint again. GitHub prompts for the new permissions and updates the existing authorization; the resulting token carries the union. See Scopes for OAuth apps ▸ Requesting updated permissions.
- GitLab: a request whose scopes differ from an earlier authorization simply shows the consent screen again and issues a token with the newly requested scopes; the user can still revoke applications from user settings.
In both cases the user, not the server, decides whether the change is acceptable, and it takes one tap rather than a documented revocation procedure.
Proposal
- In
GrantApplicationOAuth, when a grant exists and grant.Scope != form.Scope, update the grant to the approved scope after the user consents, instead of failing. The consent page already renders the requested scopes; it could highlight which ones are new (and which are being dropped, if the client requested fewer).
- In
AuthorizeOAuth, when a confidential or trusted application has an existing grant whose scope does not cover the requested scope, fall through to the consent page instead of silently reusing the old grant.
- Decide and document what happens to tokens issued under the previous scope. Refresh tokens tied to that grant would need to be invalidated, or the token contents recomputed on refresh, so an old token cannot keep using a scope the user has since removed.
- If for some reason a request must still be rejected, return a spec-defined error (
invalid_scope, or access_denied when the user declines) rather than server_error, which currently reads like a Gitea bug to the client.
I am happy to work on a PR if the direction is acceptable, in particular on whether updating the grant should store exactly the requested scope or the union of the old and new scopes.
Screenshots
n/a
Gitea Version
main (behavior also present in 1.27.x)
Feature Description
When a client asks for a scope set that differs from the user's existing OAuth2 grant, Gitea rejects the authorization instead of letting the user approve the change. The only way forward is for the user to manually revoke the application under Settings ▸ Applications ▸ Authorized OAuth2 Applications and authorize again.
In
routers/web/auth/oauth2_provider.go(GrantApplicationOAuth,mainaround line 430):For a public client (a mobile or desktop app), the consent page is always shown, so the user reads the permission list, presses Authorize, and then receives
error=server_error&error_description=a+grant+exists+with+different+scope. From the user's point of view the app is broken, and every client has to ship documentation explaining a manual revocation step.There is a second, quieter problem in the same file (
AuthorizeOAuth,mainaround line 324):For a confidential or trusted application with an existing grant, the requested
scopeis never compared togrant.Scope. The client receives a token carrying the previously granted scopes and no error at all — a silent scope downgrade. The token response does returnscope(response.Scope = grant.Scope), so a careful client can detect it, but nothing tells the user that the newly requested permission was not granted.The practical impact: any application that gains a feature needing an additional scope cannot ship it to its existing users without asking every one of them to revoke the authorization by hand. We hit this in a Gitea mobile client that added site-administration and package features, which need
read:admin/write:adminandread:package/write:package.How other providers behave
In both cases the user, not the server, decides whether the change is acceptable, and it takes one tap rather than a documented revocation procedure.
Proposal
GrantApplicationOAuth, when a grant exists andgrant.Scope != form.Scope, update the grant to the approved scope after the user consents, instead of failing. The consent page already renders the requested scopes; it could highlight which ones are new (and which are being dropped, if the client requested fewer).AuthorizeOAuth, when a confidential or trusted application has an existing grant whose scope does not cover the requested scope, fall through to the consent page instead of silently reusing the old grant.invalid_scope, oraccess_deniedwhen the user declines) rather thanserver_error, which currently reads like a Gitea bug to the client.I am happy to work on a PR if the direction is acceptable, in particular on whether updating the grant should store exactly the requested scope or the union of the old and new scopes.
Screenshots
n/a
Gitea Version
main (behavior also present in 1.27.x)