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

Swagger OAUTH Proxy to avoid CORS issue #1

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft

Swagger OAUTH Proxy to avoid CORS issue #1

wants to merge 9 commits into from

Conversation

fernando-oc
Copy link
Owner

@fernando-oc fernando-oc commented Nov 28, 2024

Greetings,

While integrating Swagger with my application, I encountered issue #5104 related to authentication in the Swagger UI. To address this, I implemented a workaround that enables the application to act as a proxy between the OAuth provider (Keycloak, in this case) and the Swagger UI.

This pull request generalizes my solution into a configuration option for the springdoc-openapi dependency, enabling easier adoption in similar use cases.


Summary of Changes

This pull request introduces the following configuration options for enabling and managing an OAuth proxy in Swagger UI. Here we have some example values:

springdoc.swagger-ui.oauth-proxy.enabled=true
springdoc.swagger-ui.oauth-proxy.path=/forward-creds
springdoc.swagger-ui.oauth-proxy.oauth-token-uri=http://localhost:keycloak/realms/ch-nets-oauth-mock/protocol/openid-connect/token

How to Use

To enable this feature in your application, follow these steps:

1. Add the configuration shown in the Summary section in your application properties

In case your authentication provider adheres to OpenID Connect and you have defined the Spring Security OAuth2 client properties using issuer URI, then you could reference the token URI via the issuer URI:

springdoc.swagger-ui.oauth-proxy.oauth-token-uri=${spring.security.oauth2.client.provider.my-oauth-server.issuer-uri}/protocol/openid-connect/token

2. Add an OpenAPI Configuration Class

Create a configuration class with the following annotations and setup:

@OpenAPIDefinition(
    servers = { @Server(url = "/") }, 
    info = @Info(title = "Service APIs", description = "Example", version = "v1.0"))
@SecurityScheme(
    name = "BearerAuth", 
    type = SecuritySchemeType.OAUTH2, 
    flows = @OAuthFlows(clientCredentials = @OAuthFlow(
        tokenUrl = "${springdoc.swagger-ui.oauth-proxy.path}",
        scopes = { @OAuthScope(name = "openid", description = "openid scope") }
    ))
)
@Configuration
public class SwaggerUiConfiguration {
}

Mainly this is an example, the most important parts to take into account are inside the @SecurityScheme:

    name = "BearerAuth" 

and inside the @OAuthFlow referencing the proxy path:

    tokenUrl = "${springdoc.swagger-ui.oauth-proxy.path}",

3. Configure the Security Filter Chain

If the user has a SecurityConfig class, it might look like this:

@Configuration
public class SecurityConfig {

  @Autowired
  SwaggerOauthProxyProperties oauthProxyProperties;

  @Bean
  public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .sessionManagement(customizer -> customizer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .csrf(AbstractHttpConfigurer::disable)
        .authorizeHttpRequests(customizer -> customizer
            .requestMatchers(HttpMethod.POST, oauthProxyProperties.getPath().getPath()).permitAll()
            .requestMatchers("/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**", "/swagger-resources/**").permitAll()
            .anyRequest().authenticated());
    return http.build();
  }

}

To use this proxy the user needs to @Autowired the OauthProxy properties:

  @Autowired
  SwaggerOauthProxyProperties oauthProxyProperties;

And add add the proxy path to SecurityFilterChain to allow POST requests:

.requestMatchers(HttpMethod.POST, oauthProxyProperties.getPath().getPath()).permitAll()

Additional notes

  • I am aware that the Configuration files are Enabled via property binding, but I am not very familiar with this and I have used in the SwaggerConfig.java
@EnableConfigurationProperties(SwaggerOauthProxyProperties.class)
  • I have created my own SNAPSHOT of the springdoc-openapi to test the functionality locally and importing it to my project, but I did not create the tests in the dependency itself, as I have not done it before. If needed, please, let me know how to do them or which are the minimum requirements.
  • I have seen that there is always a reactive alternative for the code on spring-webflux, but for now I have only considered and tested this option with restClient.

Please let me know if further details or changes are required. Sorry for the clumsiness, and thank you for considering this contribution! It is actually my first one.

@Jiguro
Copy link
Collaborator

Jiguro commented Nov 28, 2024

@fernando-oc fernando-oc changed the title Swagger OAUTH Proxy to avoid CORS issue Draft: Swagger OAUTH Proxy to avoid CORS issue Nov 28, 2024
@fernando-oc fernando-oc changed the title Draft: Swagger OAUTH Proxy to avoid CORS issue Swagger OAUTH Proxy to avoid CORS issue Nov 28, 2024
@fernando-oc fernando-oc marked this pull request as draft November 28, 2024 09:22
@fernando-oc fernando-oc requested a review from Jiguro November 28, 2024 09:22
@fernando-oc fernando-oc marked this pull request as ready for review November 28, 2024 11:59
return response.getBody();

} else {
throw new RuntimeException("Authorization header missing or not using Basic Auth");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there maybe from Spring a more specific Exception we can use to indicate 401 Unauthorized because Basic Auth header is missing?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have changed it to a ResponseStatusException

public class SwaggerOauthProxyController {

private final String GRANT_TYPE_KEY = "grant_type";
private final String CLIENT_CREDENTIALS_KEY = "client_credentials";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this is the value in a Map entry, let's maybe call it CLIENT_CREDENTIALS_VALUE.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, much better, thanks!

@Jiguro
Copy link
Collaborator

Jiguro commented Nov 28, 2024

Maybe the following paragraph can be added under section 1. of the PR's README:

1. Add the configuration shown in the Summary section in your application properties

In case your authentication provider adheres to OpenID Connect and you have defined the Spring Security OAuth2 client properties using issuer URI, then you could reference the token URI via the issuer URI:

springdoc.swagger-ui.oauth-proxy.oauth-token-uri=${spring.security.oauth2.client.provider.my-oauth-server.issuer-uri}/protocol/openid-connect/token

@fernando-oc fernando-oc marked this pull request as draft November 28, 2024 13:45
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.

2 participants