Skip to content
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

How to implement Security Bootstrapping for the OAuth2 code flow? #549

Open
benfrancis opened this issue Jul 23, 2024 · 2 comments
Open
Labels
bug Something isn't working

Comments

@benfrancis
Copy link
Member

I'm trying to implement Security Bootstrapping using the OAuth2 code flow.

The WoT Discovery specification says "Security bootstrapping MAY be provided on any HTTP endpoint that serves a TD." In OAuth2 terminology, the HTTP server which serves a TD would be the "resource server", which may be separate from the "authorization server" which serves the authorization endpoint, and issues access tokens.

Therefore the way I imagine this to work is that a client initially sends an HTTP GET request to a Thing Server (resource server) with the URL of a Thing Description but gets a response which indicates that the resource requires authentication and how to authenticate, including the URL of the authorization endpoint.

However, in reading the relevant RFCs I'm not clear on how this is supposed to work. Specifically, how does the client get from the resource URL to the authorization URL? When authenticating access to interaction affordance endpoints of a Thing this information is contained in the security metadata of the Thing Description, but what about when authenticating access to the Thing Description itself?

The WoT Discovery specification says:
"The relevant OAuth2 flow, the code flow, instead of a 401 response begins with a redirection to an authentication server, eventually resulting in credentials (bearer tokens in the case of WoT) that can be used for access."

It is true that RFC 6749 (The OAuth 2.0 Authorization Framework) says that in the Authorization Code flow "The client initiates the flow by directing the resource owner's user-agent to the authorization endpoint.". But in the context of WoT Security Bootstrapping, how does a client discover the URL of the authorization endpoint?

Assuming the Thing Server (resource server) is using HTTP Bearer authentication, RFC 6750 (The OAuth 2.0 Authorization Framework: Bearer Token Usage) does in fact suggest that if no token is provided in a protected resource request, it should respond with a 401 response containing a WWW-Authenticate header. E.g.

     HTTP/1.1 401 Unauthorized
     WWW-Authenticate: Bearer realm="example"

Should the URL of the authorization endpoint somehow be included in the WWW-Authenticate header of response to an unauthenticated request to a Thing Description URL, and if so how?

Paging @mmccool who I recall wrote this part of the WoT Discovery specification.

(This may be a general OAuth2 question, but I'm not familiar enough with implementing those specifications to know how that flow is usually initiated, and therefore whether this is a novel use case).

@benfrancis benfrancis added the bug Something isn't working label Jul 25, 2024
@benfrancis
Copy link
Member Author

benfrancis commented Jul 25, 2024

Having discussed this in the Web of Things Community Group Discord server and then done some more research, I have learned the following...

The OAuth 2.0 Authorization Framework core specification (RFC 6749) does not provide a mechanism to discover an authorization and token endpoint from a resource URL.

The OAuth 2.0 Authorization Server Metadata specification (RFC 8414) defines a standard metadata format and well known URL path to retrieve metadata about an authorization server, but it does not provide a mechanism to discover the authorization server from a resource URL.

OpenID Connect from the OpenID Foundation (which builds on top of OAuth2) does provide a solution to this problem in the OpenID Connect Discovery specification, using WebFinger (RFC 7033).

This starts by asking the user provide a user ID (which can take multiple forms), which is then used to derive the URL of a WebFinger endpoint, which then returns the URL of the authorization server for that user, which is then used to derive the well-known URL of an authorization metata resource, which contains the authorization and token endpoints!

This is quite an involved process which is particularly tricky for our use case, because the domain of the authorization server (an edge IoT gateway) is different for each user. Even though in our particular implementation the authorization endpoint could trivially be derived from the Thing URL, in order to conform with the OpenID Connect standard it would require a user to separately provide a Thing Description URL and an identifier in the unusual form of joebloggs@gmail.com@acme.webthings.io in order to construct a useable acct: URL. This would result in a very confusing user experience.

As an aside, having never implemented OAuth2 from scratch before, I'm quite shocked at the number of different specifications from different sources that need to be cobbled together in order to create a system actually works and doesn't require the client, resource server and authorization server to already have prior knowledge of each other (to avoid the need for a hard-coded list of client IDs you may also need to implement something like IndieAuth).

In conclusion, I think security bootstrapping for the OAuth2 code flow is essentially broken because there is no standardised way (at least within the IETF's OAuth2 specifications) to derive an authorization and token endpoint from a thing URL. I think this could be considered a bug in the WoT Discovery specification, so I am labelling this issue as such.

I hope someone can point me towards a solution and prove me wrong! In particular, it would be great if there was a standardised way to link to an OAuth2 authorization endpoint and token endpoint (or at least the authorization server's domain) in the WWW-Authenticate header of an HTTP 401 response.

It actually looks like this was possible with OAuth 1.0 (RFC 5849), but I'm assuming there was a reason this was changed.

Does anyone have any other ideas how to solve this?

@benfrancis
Copy link
Member Author

I note that some OAuth2 implementations include additional parameters in the WWW-Authenticate header (e.g. graph.microsoft.com includes an authorization_uri parameter and yourVault.vault.azure.net includes an authorization parameter to tell the client where to find the authorization endpoint). Although a much more straightforward way of discovering an authorization endpoint, this doesn't appear to be standardised.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant