Modify

Opened 14 months ago

Closed 14 months ago

Last modified 14 months ago

#23733 closed defect (invalid)

JOSM does not send client_secret in oauth2 token request for authorization code grant flow

Reported by: Woazboat Owned by: Woazboat
Priority: critical Milestone:
Component: Core Version: latest
Keywords: oauth2, client_secret Cc:

Description (last modified by Woazboat)

Encountered on JOSM v19103 when using a local self-hosted OSM server

oauth2 settings

After pressing the 'Authorize now (Fully automatic)' button, the following HTTP exchange happens:

Authorization request -> OSM server

GET /oauth2/authorize?response_type=code&client_id=Z6bOm_8NT2JIEx5JDpV4iQwy1vD40Pc6HYAu4ugUWgc&redirect_uri=http://127.0.0.1:8111/oauth_authorization&scope=read_gpx%20write_gpx%20read_prefs%20write_prefs%20write_api%20write_notes&state=9c24c6cd-5572-410f-bab3-fa49c91a71cd&code_challenge_method=S256&code_challenge=JjuiikdPYf6hiXWwzZW5ydJur-lqn7AdC_mH0WBORDA HTTP/1.1
Host: localhost:31500
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
DNT: 1
Connection: keep-alive
Cookie: _osm_session=f1dd39db183fe58b9806ee39742ecc9f
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1

OSM server sends the authorization code back to JOSM via the remote control url:

HTTP/1.1 302 Found
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 0
X-Content-Type-Options: nosniff
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Content-Language: en
Location: http://127.0.0.1:8111/oauth_authorization?code=1Zhog9xRXeAsbRwVUgVvtubQkmWeBuY_kr7SY_pkVy8&state=9c24c6cd-5572-410f-bab3-fa49c91a71cd
Content-Type: text/html; charset=utf-8
Cache-Control: no-cache
Content-Security-Policy-Report-Only: default-src 'self'; child-src 'self'; connect-src 'self'; font-src 'none'; frame-ancestors 'self'; frame-src 'self'; img-src 'self' data: www.gravatar.com *.wp.com tile.openstreetmap.org gps.tile.openstreetmap.org *.tile.thunderforest.com tile.tracestrack.com *.openstreetmap.fr; manifest-src 'self'; media-src 'none'; object-src 'self'; script-src 'self'; style-src 'self' 'nonce-rbBl3U4wpaHEQJMZrruF+qw9tDDD+sXC'; worker-src 'none'
Set-Cookie: _osm_session=f1dd39db183fe58b9806ee39742ecc9f; path=/; expires=Thu, 11 Jul 2024 13:17:31 GMT; HttpOnly; SameSite=Lax
X-Request-Id: cc4fcc48-4d44-4d1c-80b2-600ff2ee7d2f
X-Runtime: 0.120389
Server-Timing: start_processing.action_controller;dur=0.09, cache_read.active_support;dur=0.14, sql.active_record;dur=49.07, instantiation.active_record;dur=2.14, transaction.active_record;dur=16.65, redirect_to.action_controller;dur=0.14, process_action.action_controller;dur=87.95, cache_write.active_support;dur=0.10
vary: Accept-Language, Origin
Content-Length: 0
Date: Thu, 13 Jun 2024 13:17:31 GMT
Server: lighttpd/1.4.64
GET /oauth_authorization?code=1Zhog9xRXeAsbRwVUgVvtubQkmWeBuY_kr7SY_pkVy8&state=9c24c6cd-5572-410f-bab3-fa49c91a71cd HTTP/1.1
Host: 127.0.0.1:8111
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
DNT: 1
Connection: keep-alive
Cookie: _xsrf=2|1ee5c13c|6a4925189a1640610ef6a475ff957ba5|1617984267; CSRF-Token-3KZHS=uydXZvUwRpA3Jud2FgNt4i3QUmFp7PUu; session=eyJjc3JmX3Rva2VuIjoiNWJlYjY1N2I0YTk0OTQwY2Y1M2RlN2EyYmNkMGEyODYxNzkzOTQ5MSJ9.Yq0O7g.ckj-pvRrRe21b4q7kb41nhU9tZQ
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Sec-GPC: 1

In response, JOSM sends a POST request to the token access URL to fetch the token. The body of this request is missing the required client_secret field.

POST /oauth2/token HTTP/1.1
User-Agent: JOSM/1.5 (19103 en) Linux Debian GNU/Linux trixie/sid Java/17.0.11
Accept: */*
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Host: localhost:31500
Connection: keep-alive
Content-Length: 238
Cookie: _osm_session=04f15c9d111f742de724847cd34caa4b

grant_type=authorization_code&client_id=Z6bOm_8NT2JIEx5JDpV4iQwy1vD40Pc6HYAu4ugUWgc&redirect_uri=http://127.0.0.1:8111/oauth_authorization&code=1Zhog9xRXeAsbRwVUgVvtubQkmWeBuY_kr7SY_pkVy8&code_verifier=f7cfbb5d-87a2-4169-9cf3-62ed0020a9e4

As a result, the server rejects the token request:

HTTP/1.1 401 Unauthorized
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 0
X-Content-Type-Options: nosniff
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Cache-Control: no-store
Content-Type: application/json; charset=utf-8
WWW-Authenticate: Bearer realm="Doorkeeper", error="invalid_client", error_description="Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method."
Content-Security-Policy-Report-Only: default-src 'self'; child-src 'self'; connect-src 'self'; font-src 'none'; form-action 'self'; frame-ancestors 'self'; frame-src 'self'; img-src 'self' data: www.gravatar.com *.wp.com tile.openstreetmap.org gps.tile.openstreetmap.org *.tile.thunderforest.com tile.tracestrack.com *.openstreetmap.fr; manifest-src 'self'; media-src 'none'; object-src 'self'; script-src 'self'; style-src 'self' 'nonce-XzjPNRrxCTsi4OWcceuk1Z/uJARIa2kH'; worker-src 'none'
X-Request-Id: 1e9532ba-6621-4a14-8eb3-078c28565325
X-Runtime: 0.032401
Server-Timing: start_processing.action_controller;dur=0.02, sql.active_record;dur=1.51, instantiation.active_record;dur=0.29, process_action.action_controller;dur=12.66
vary: Accept, Origin
Content-Length: 173
Date: Thu, 13 Jun 2024 13:17:31 GMT
Server: lighttpd/1.4.64

{"error":"invalid_client","error_description":"Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method."}

After manually modifying and re-sending the POST request to include the client_secret field, the request succeeds and the server responds with a token:

(The following request logs for the modified request are from a different attempt, so the authorization codes/nonces are different)

POST /oauth2/token HTTP/1.1
User-Agent: JOSM/1.5 (19103 en) Linux Debian GNU/Linux trixie/sid Java/17.0.11
Accept: */*
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Host: localhost:31500
Connection: keep-alive
Content-Length: 296
Cookie: _osm_session=04f15c9d111f742de724847cd34caa4b

grant_type=authorization_code&client_id=Z6bOm_8NT2JIEx5JDpV4iQwy1vD40Pc6HYAu4ugUWgc&client_secret=2TvpyFlxvu0C4b435d3lofB5dY7K4_qSKWoX6C0LEWM&redirect_uri=http://127.0.0.1:8111/oauth_authorization&code=IO4jNs0TqwV4o0Qxc0Itr1k-EzkttoI4s_EiAA5VxeA&code_verifier=bb1210fd-b70f-4d5b-b260-2264a2249f47

HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 0
X-Content-Type-Options: nosniff
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Cache-Control: no-store
Content-Type: application/json; charset=utf-8
ETag: W/"3ac628dafb309e963cfeaf02f5dab579"
Content-Security-Policy-Report-Only: default-src 'self'; child-src 'self'; connect-src 'self'; font-src 'none'; form-action 'self'; frame-ancestors 'self'; frame-src 'self'; img-src 'self' data: www.gravatar.com *.wp.com tile.openstreetmap.org gps.tile.openstreetmap.org *.tile.thunderforest.com tile.tracestrack.com *.openstreetmap.fr; manifest-src 'self'; media-src 'none'; object-src 'self'; script-src 'self'; style-src 'self' 'nonce-i9hz2NMTdzxzWhLUI8Z4eCIksw+JEXzN'; worker-src 'none'
X-Request-Id: b7b5e3e1-84a5-4f58-a090-269eed9e7620
X-Runtime: 0.051822
Server-Timing: start_processing.action_controller;dur=0.02, sql.active_record;dur=13.24, instantiation.active_record;dur=0.73, transaction.active_record;dur=18.50, process_action.action_controller;dur=33.36
vary: Accept, Origin
Content-Length: 182
Date: Thu, 13 Jun 2024 13:30:25 GMT
Server: lighttpd/1.4.64

{"access_token":"oxpk2rfgDuAlRMP0T25b5v3LNXJF99PK1451H8Rm1wM","token_type":"Bearer","scope":"read_gpx write_gpx read_prefs write_prefs write_api write_notes","created_at":1718284075}

The server is only used for local testing, so posting the secrets here is not an issue


References:
https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1
https://developer.okta.com/blog/2018/04/10/oauth-authorization-code-grant-type
https://www.oauth.com/playground/authorization-code.html

Attachments (1)

josm_custom_server_oauth2_settings.png (148.4 KB ) - added by Woazboat 14 months ago.
oauth2 settings

Download all attachments as: .zip

Change History (7)

by Woazboat, 14 months ago

oauth2 settings

comment:1 by Woazboat, 14 months ago

Description: modified (diff)

comment:2 by Woazboat, 14 months ago

Description: modified (diff)

comment:3 by taylor.smock, 14 months ago

RFC6749 section 2.3.1 ("Client Password") isn't currently supported by JOSM.

For starters, JOSM is not a "confidential" application -- since we distribute a jar file with the parameters, the client_secret is by definition not secret. From section 2.1, when generating tokens for JOSM on osm.org, I chose the "public" (non-confidential) option. I only added the secret for the dev instance to make it easier for contributors to test functionality that required it.

We use the Authorization Code Grant flow (section 4.1).

Specifically:
4.1.1: No client secret
4.1.2: No client secret
4.1.3: No client secret

OK. Where does the confidential application actually use the client secret:
2.3.1: Basic auth with authorization server. From what I understand, this should be disabled on osm.org anyway.
For this flow, I would have a user ("user") and password ("password123") which would be Authorization: Basic dXNlcjpwYXNzd29yZDEyMw== with a POST like /token and a body of grant_type=token&client_id=${client_id}&client_secret=${client_secret}.
All that we can do with the client_secret is avoid opening a browser; this is generally considered bad practice for 3p clients anyway.

In any case, I specifically wrote our OAuth 2 implementation with a view towards "best practices".

comment:4 by taylor.smock, 14 months ago

Owner: changed from team to Woazboat
Status: newneedinfo

I think your client_id might be wrong. Can you show the oauth2 settings from your local OSM server?

in reply to:  3 comment:5 by Woazboat, 14 months ago

Ah, yes you are correct, this was a configuration error on my side and a misunderstanding about oauth. I left the 'confidential application' checkbox on the default 'on' state when registering the oauth application on the server. The token access request works after deselecting that setting.

Replying to taylor.smock:

OK. Where does the confidential application actually use the client secret:
2.3.1: Basic auth with authorization server. From what I understand, this should be disabled on osm.org anyway.
For this flow, I would have a user ("user") and password ("password123") which would be Authorization: Basic dXNlcjpwYXNzd29yZDEyMw== with a POST like /token and a body of grant_type=token&client_id=${client_id}&client_secret=${client_secret}.

As far as I understand it, only one of either basic auth or the client secret in the body should be used for client authentication. At least with the default configuration, the rails port accepts the client secret as authentication (as seen above). Not sure if the configuration for osm.org differs.

I think your client_id might be wrong. Can you show the oauth2 settings from your local OSM server?

The OSM website source doesn't include the default client id config for josm, so the one in the requests above is a custom one for a self-registered oauth application.

Last edited 14 months ago by Woazboat (previous) (diff)

comment:6 by Woazboat, 14 months ago

Resolution: invalid
Status: needinfoclosed

Modify Ticket

Change Properties
Set your email in Preferences
Action
as closed The owner will remain Woazboat.
as The resolution will be set.
The resolution will be deleted. Next status will be 'reopened'.

Add Comment


E-mail address and name can be saved in the Preferences .
 
Note: See TracTickets for help on using tickets.