Skip to content

feat(oauth-providers): add OpenStreetMap OAuth 2.0 provider - #2076

Open
smellman wants to merge 3 commits into
honojs:mainfrom
smellman:dev/issue-2074
Open

feat(oauth-providers): add OpenStreetMap OAuth 2.0 provider#2076
smellman wants to merge 3 commits into
honojs:mainfrom
smellman:dev/issue-2074

Conversation

@smellman

@smellman smellman commented Aug 7, 2026

Copy link
Copy Markdown

Closes #2074.

Adds openstreetmapAuth, exported as @hono/oauth-providers/openstreetmap, plus a revokeToken helper.

import { Hono } from 'hono'
import { openstreetmapAuth } from '@hono/oauth-providers/openstreetmap'

const app = new Hono()

app.use(
  '/openstreetmap',
  openstreetmapAuth({
    client_id: process.env.OPENSTREETMAP_ID,
    client_secret: process.env.OPENSTREETMAP_SECRET,
    scope: ['read_prefs'],
  })
)

app.get('/openstreetmap', (c) => {
  return c.json({
    token: c.get('token'),
    grantedScopes: c.get('granted-scopes'),
    user: c.get('user-openstreetmap'),
  })
})

Implementation notes

OpenStreetMap runs Doorkeeper, and a few of its
choices differ from the other providers in this package. Each decision below is backed by the
authorization server metadata
or by the openstreetmap-website source.

PKCE is always used, with the S256 challenge method. The server advertises
code_challenge_methods_supported: ["plain", "S256"], and Doorkeeper requires a code verifier for
public clients. Sending the challenge unconditionally means the middleware works for both
confidential and public applications.

No refresh token, and the access token carries no expires_in. OpenStreetMap's Doorkeeper
config sets access_token_expires_in nil and leaves use_refresh_token disabled, so tokens do not
expire and no refresh token is ever issued. token is therefore { token: string } and
refresh-token is not set. revokeToken is exported instead, since revocation is the only way to
invalidate a token.

User details come from GET /api/0.6/user/details.json. This carries much more than the OIDC
userinfo endpoint, but it needs the read_prefs scope — api_ability.rb has
can :details, User if scopes.include?("read_prefs") — so the README calls that out. The
OpenStreetMap API answers errors with a plain text body rather than JSON, so the response status is
checked before parsing (authFlow.ts).

skip_authorization is excluded from OpenStreetMapScope, and read_email is documented as
privileged. Both live in PRIVILEGED_SCOPES in lib/oauth.rb and are only offered to applications
registered by a site administrator, so user-openstreetmap.email is normally undefined.

Testing

Unit tests use the existing msw setup: 11 cases covering the redirect URL and its PKCE parameters,
the custom redirect_uri and state options, CSRF protection (both a mismatched and an omitted
state), an invalid code, an API rejection of the token, the success path, and both revocation
outcomes.

Beyond that, the flow was run manually against the live openstreetmap.org authorization server —
authorization, token exchange, user lookup and revocation. The live response then corrected the
types: contributor_terms.pd and messages are unconditional in _user.json.jbuilder for a
request that gets past the read_prefs check, so both are required rather than optional.

One trap worth recording for anyone testing this locally: force_ssl_in_redirect_uri in
doorkeeper.rb only permits plain http for the hosts 127.0.0.1 and ::1. A redirect URI of
http://localhost:3000/... is rejected at application registration time.


The author should do the following, if applicable

  • Add tests
  • Run tests
  • pnpm changeset at the top of this repo and push the changeset
  • Follow the contribution guide

@changeset-bot

changeset-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0e7b1f4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@hono/oauth-providers Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@smellman

smellman commented Aug 7, 2026

Copy link
Copy Markdown
Author

@codecov

codecov Bot commented Aug 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.39%. Comparing base (ee78224) to head (0e7b1f4).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2076      +/-   ##
==========================================
+ Coverage   92.23%   92.39%   +0.16%     
==========================================
  Files         116      119       +3     
  Lines        4122     4210      +88     
  Branches     1077     1096      +19     
==========================================
+ Hits         3802     3890      +88     
  Misses        285      285              
  Partials       35       35              
Flag Coverage Δ
oauth-providers 93.28% <100.00%> (+0.80%) ⬆️
zod-openapi 95.53% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add OpenStreetMap OAuth 2.0 provider

1 participant