0

Problem description: I am trying to secure a resource with OAuth2 access authorization, where the path of the resource indicates ownership by a resource owner.

  • That is to say I do not want simply give access to a Client based on general regex URL/Scope matching. I want requests coming from the Client to undergo a security constraint that matches the individual resource ownership of the Resource Server URL (e.g. indicated by a URL parameter) on the access token provided by the client.
  • I want to understand if (and if yes, how) this can be done with only OAuth2. I have doubts, as it is an authorization protocol, not an authentication protocol. It seems for authentication purposes it is often combined with OpenID, so I wonder if that is what is blocking me here.

Extra requirements:

What I‘ve found: I found a tutorial and sample code by Baeldung. It works fine and is close to the setup I want, but it grants access using scopes, which are general URL regex matchers. That is to say in the referenced example the Client (service) obtains access for parts of the Resource Server‘s REST API, but there is no notion of user ownership for the resources, e.g. as part of the URL. That is to say, the resources in question seem to belong to the Resource Server admin, not individual service users.

Questions:

  1. Is my understanding of OAuth2 correct, that access delegation for resources owned by service users, is a valid use case?
 E.g. can I grant access to a Client who wants to access /{someuseridentifier}/someresource on behalf of a service user: someuseridentifier? That is to say, obtain a Client that when authorized for alan can access /alan/someresource, but not ada/someresource.
  2. If that is possible, what is the clean way to do this? Intuitively I would implement a check in the REST endpoint that compares the Principal (token owner) to the value of someuseridentifier (being part of the URL). Is this the right way? Can I even retrieve the Principal, as the person who originally granted access to the resource, or is this not supported by OAuth2?
  3. If it is possible, is there is a better way than coding the comparison in the REST endpoints? could I use e.g. a WebSecurityConfigurerAdapter that intercepts inbound requests, and matches Principal on URL (and hence the actual resource owner).

I've read the OAuth2 specification, followed a setup tutorial, investigated the OAuth2 sample setup for access delegation and searched for similar issues on stackoverflow, but it seems no one else has this problem, although it is listed as common scenario in literature. I am able to configure general admin access delegation, using URL regex matching, but not on a service-user basis.

m5c
  • 45
  • 7

1 Answers1

2

Yes, this is possible both in general and specifically with OAuth2. I think it helps to know how to do this without OAuth2 first, since the same technique applies to OAuth2 and protecting resources within a Resource Server.

It is also important to note that the concept of "principal" (contained in the Authentication) on the resource server is mapped to the user who granted access to the client (usually the subject or "sub" claim of the access token), not the client (usually the audience or "aud" claim of the access token).

In fact, you can use a combination of authorization schemes (a layered approach), starting with URLs and scopes (coarse grained) and adding to it additional constraints at either the request/URL level or method level (fine grained). Consider the following example:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class ResourceServerConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // @formatter:off
        http
            .authorizeHttpRequests((authorize) -> authorize
                .requestMatchers("/*/messages").hasAuthority("SCOPE_message.read")
                .anyRequest().denyAll()
            )
            .oauth2ResourceServer((oauth2) -> oauth2
                .jwt(Customizer.withDefaults())
            );
        // @formatter:on

        return http.build();
    }

}
@RestController
public class MessagesController {

    @PreAuthorize("authentication.name == #userId")
    @GetMapping("/{userId}/messages")
    public String[] getMessages(@PathVariable("userId") String userId) {
        System.out.println(userId + " is granted access");
        return new String[] { "Message 1", "Message 2", "Message 3" };
    }

}

It's a contrived example (and the userId is meaningless) but hopefully you get the point. The access token was given the scope message.read, so the /{userId}/messages endpoint requires being granted that permission by the user, but additionally the resource server protects user's resources by requiring the resource owner's name to match the path variable using a @PreAuthorize annotation.

There are lots of other ways to do things depending on your application's domain and endpoint structure but the basics are the same. OpenID Connect is not required as the resource server does not care how the user was authenticated.

Steve Riesenberg
  • 4,271
  • 1
  • 4
  • 26
  • I'm having issues adapting this solution. The `.requestMatchers("/*/messages")` call does not accept a string argument. According to the API I should provide one or many `RequestMatcher` arguments. Am I overlooking something? – m5c Jul 12 '23 at 13:54
  • My bad, I was on a too old spring version. The Baledung guide referenced in the OP uses the a bit outdated `spring starter parent 2.5.4`. An update to `3.1.1` solved the issue. Now the IDE also recognizes the `@EnableMethodSecurity` annotation. – m5c Jul 12 '23 at 14:08
  • For completeness: I've uploaded a runnable, well documented sample that implements the described setup on [GitHub](https://github.com/m5c/spring-security-oauth) – m5c Jul 20 '23 at 16:55