-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Feat: Implement oauth authorization server metadata #1737
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Feat: Implement oauth authorization server metadata #1737
Conversation
|
@nbulaj @ransombriggs would love some feedback on my implementation proposal in the PR description — I think that's the right way forwards, but haven't yet begun to implement. |
|
I would give feedback but I am not familiar with https://www.rfc-editor.org/rfc/rfc8414 and really just a bystander on this project. I got it to do what I needed and then was moved onto other things. |
|
I have some feeling that the work should be done inside https://github.com/doorkeeper-gem/doorkeeper-openid_connect 🤔 I remember it handles all these stuff around Discovery and related. Have to recheck deeply |
RFC 8414 is part of OAuth and is separate from OIDC's Discovery, but is compatible in some ways. RFC 8414 uses |
a9b73f9 to
9266140
Compare
| root_url, | ||
| -> (**args) { url_for(**args) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not particularly keen on this, but including UrlFor or similar in the Doorkeeper::OAuth::DiscoveryResponse class didn't work as I'd expect it to, and I need the url builder in order to generate the absolute URLs for the authorization server metadata.
| # Issuer URL for the OAuth Authorization Server | ||
| option :issuer, default: nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically this is maybe OpenID more, but OAuth 2.0 does have a spec for it, see: #1720
| # Allows setting a hash of custom data on the OAuth 2.0 Authorization | ||
| # Server Metadata discovery response. | ||
| option :custom_discovery_data, default: {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is intended for applications to use, e.g., Mastodon where we want to set service_documentation and app_registration_url properties in the metadata.
|
|
||
| def grant_types_supported | ||
| grant_types_supported = config.grant_flows.dup | ||
| grant_types_supported << 'refresh_token' if !!config.refresh_token_enabled? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is technically broken, because use_refresh_token configuration can be a block/proc, which accepts server and context which, well, I don't think we have here, nor would it be possible to have?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking about this more, if we did drastically change how refresh tokens work, as suggested in #1771, then we could have options like:
Doorkeeper.configure do
orm :active_record
# Enable using them in general
use_refresh_tokens true
# Set the expiry for refresh tokens:
refresh_token_expires_in 30.days
# Allow refresh tokens to only be used by specific clients:
allow_refresh_tokens_for do |client, access_grant|
access_grant.scopes.exists?('offline_access')
end
endOr something.
| expect(json_response).to have_key("issuer") | ||
| expect(json_response).to have_key("authorization_endpoint") | ||
| expect(json_response).to have_key("token_endpoint") | ||
| expect(json_response).to have_key("revocation_endpoint") | ||
| expect(json_response).to have_key("userinfo_endpoint") | ||
| expect(json_response).to have_key("scopes_supported") | ||
| expect(json_response).to have_key("response_types_supported") | ||
| expect(json_response).to have_key("response_modes_supported") | ||
| expect(json_response).to have_key("grant_types_supported") | ||
| expect(json_response).to have_key("token_endpoint_auth_methods_supported") | ||
| expect(json_response).to have_key("code_challenge_methods_supported") | ||
|
|
||
| expect(json_response["issuer"]).to be_a(String) | ||
|
|
||
| expect(json_response["scopes_supported"]).to be_a(Array) | ||
| expect(json_response["response_types_supported"]).to be_a(Array) | ||
| expect(json_response["response_modes_supported"]).to be_a(Array) | ||
| expect(json_response["grant_types_supported"]).to be_a(Array) | ||
| expect(json_response["token_endpoint_auth_methods_supported"]).to be_a(Array) | ||
| expect(json_response["code_challenge_methods_supported"]).to be_a(Array) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if I should really test logic of the discovery response here, so instead I'm just asserting properties and types for things that must be arrays.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very similar to what we did for a one-off in our application:
get oauth_discovery_path
expect(response).to have_http_status(200)
expect(response.content_type).to eq("application/json; charset=utf-8")
object = JSON.parse(response.body)
expect(object.dig("authorization_endpoint")).to eq("http://www.example.com/oauth/authorize")
expect(object.dig("token_endpoint")).to eq("http://www.example.com/oauth/token")
expect(object.dig("revocation_endpoint")).to eq("http://www.example.com/oauth/revoke")
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, similar to the Mastodon one as well: https://github.com/mastodon/mastodon/blob/main/spec/requests/well_known/oauth_metadata_spec.rb
I'm just not sure I want to be validating the values or not here, since the paths can be configured with doorkeeper.
|
@nbulaj @ransombriggs I think this is ready for a look, I think it works okay for now? Not sure if we need to do something else or not, there's a few things I've noted in the comments above. |
| it "returns json" do | ||
| get "/.well-known/oauth-authorization-server" | ||
|
|
||
| response_status_should_be(200) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should add a content-type assertion here as well.
| end | ||
|
|
||
| def scopes_supported | ||
| config.scopes.to_a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be better as:
| config.scopes.to_a | |
| config.scopes.map(&:to_s) |
Might depend on how Scopes implements to_a
|
|
||
| # FIXME: https://github.com/doorkeeper-gem/doorkeeper/pull/1770 | ||
| def token_endpoint_auth_methods_supported | ||
| %w(none client_secret_basic client_secret_post) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will be fixed by #1772
|
Is this kind of forgotten or is it someway to enable a |
|
@alex-ross this isn't forgotten, work is just progressing more slowly because of time constraints |
Summary
This is early work on supporting #1587, it's currently based off an older commit from #1732, #1735, and #1736 since those allow me to work semi-productively on the doorkeeper codebase.
I'm unsure if we'd really need a custom controller for discovery, as I think the initializer for configuration should probably handle the "adding extra stuff to the
.well-known/oauth-authorization-serverendpoint, instead of actually overriding the controller.I don't think it makes sense for the
scopeoption with doorkeeper to affect the.well-known/oauth-authorization-serverendpoint, however, nesting that within a scope can work (allowing multiple oauth providers on the one domain, similar to how keycloak works)I'm still to figure out all the necessary stuff for actually building out the data for response. I think I'm probably inclined to create a
Doorkeeper::OAuth::DiscoveryResponseclass, and acustom_discovery_responseconfiguration block option, then have the controller just apply that class.There's also the question of "should this be rename-able by other plugins?", e.g., OIDC that uses
.well-known/openid-configurationinstead of.well-known/oauth-authorization-server— I'd probably guess maybe no, and instead the OIDC plugin could just remove the.well-known/oauth-authorization-serverroute and add its own which is calling a subclass ofDoorkeeper::OAuth::DiscoveryResponse?