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

Is there a way to authorize users using OpenID Connect Discovery when POST endpoints require CSRF protection? #7750

Closed
little-pinecone opened this issue Jan 11, 2022 · 1 comment

Comments

@little-pinecone
Copy link

Q&A (please complete the following information)

  • OS: Ubuntu 20.04
  • Browser: chrome
  • Version: 97.0.4692.71 (Official Build) (64-bit)
  • Method of installation: maven (through org.springdoc:springdoc-openapi-ui)
  • Swagger-UI version: 4.1.3
  • Swagger/OpenAPI version: OpenAPI 3.0.1

Content & configuration

Swagger/OpenAPI definition:

openapi: 3.0.1
info:
  title: spring-boot-swagger-ui-keycloak
  description: Demo Spring Boot app showing Swagger UI secured with Keycloak
  license:
    name: Unlicense
    url: https://unlicense.org/
  version: 0.0.1-SNAPSHOT
servers:
  - url: http://localhost:8080
    description: Generated server url
security:
  - openId: []
tags:
  - name: Products
paths:
  /api/products:
    get:
      tags:
        - Products
      summary: Return all products
      operationId: findAll
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Product'
    post:
      tags:
        - Products
      summary: 'Save a product (available for the chief-operating-officer role, default user: ''christina'')'
      operationId: save
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Product'
        required: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Product'
  /api/products/{productId}:
    get:
      tags:
        - Products
      summary: Return a product by its id
      operationId: findById
      parameters:
        - name: productId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Product'
components:
  schemas:
    Product:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        color:
          type: string
        ean:
          type: string
        countryOfOrigin:
          type: string
        price:
          type: string
        availableQuantity:
          type: integer
          format: int32
  securitySchemes:
    openId:
      type: openIdConnect
      openIdConnectUrl: http://127.0.0.1:8024/auth/realms/Spring-Boot-Example/.well-known/openid-configuration
package in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.swagger.authorization;

import in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.security.KeycloakProperties;
import in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.swagger.ApiInfoProvider;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@OpenAPIDefinition
@AllArgsConstructor
@ConditionalOnProperty(name = "security.config.openid-discovery", havingValue = "true")
public class SwaggerOpenIdConfig {

    private static final String OPEN_ID_SCHEME_NAME = "openId";
    private static final String OPENID_CONFIG_FORMAT = "%s/realms/%s/.well-known/openid-configuration";

    @Bean
    OpenAPI customOpenApi(KeycloakProperties keycloakProperties, ApiInfoProvider infoProvider) {
        var openAPI = new OpenAPI()
                .info(infoProvider.provide());

        addSecurity(openAPI, keycloakProperties);

        return openAPI;
    }

    private void addSecurity(OpenAPI openApi, KeycloakProperties properties) {
        Components components = createComponentsWithSecurityScheme(properties);
        openApi
                .components(components)
                .addSecurityItem(new SecurityRequirement().addList(OPEN_ID_SCHEME_NAME));
    }

    private Components createComponentsWithSecurityScheme(KeycloakProperties properties) {
        SecurityScheme openIdScheme = createOpenIdScheme(properties);

        return new Components()
                .addSecuritySchemes(OPEN_ID_SCHEME_NAME, openIdScheme);
    }

    private SecurityScheme createOpenIdScheme(KeycloakProperties properties) {
        String connectUrl = String.format(OPENID_CONFIG_FORMAT, properties.getAuthServerUrl(), properties.getRealm());

        return new SecurityScheme()
                .type(SecurityScheme.Type.OPENIDCONNECT)
                .openIdConnectUrl(connectUrl);
    }
}

Example project

To demonstrate my problem, I created an example Spring Boot project with a dockerized Keycloak server: Demo Spring Boot app showing Swagger UI secured with Keycloak.

The project has csrf protection enabled as configured in the SecurityConfig.java class. To make the POST request work, the project configures Springodoc to enable CSRF support.

By default, the project uses the implicit flow to authorize in Swagger UI as it's the only method that works with CSRF enabled and Keycloak. To test other authorization methods, check out the Experimental Authorization configurations for Swagger UI section in project's README.md.

How can we help?

The only Swagger UI config that works is for the implicit flow. However, as we can read in the Open API specification: "Please note that as of 2020, the implicit flow is about to be deprecated by OAuth 2.0 Security Best Current Practice. Recommended for most use case is Authorization Code Grant flow with PKCE." (see Security Scheme Object).

At the moment, the Swagger configs in the example project for the OpenID Connect Discovery scheme, the Authorization Code and Password flows won't work with Spring Boot csrf protection enabled for Springdoc. To see Available authorizations I have to disable Springdoc CSRF support which breaks POST endpoints. Otherwise, the Available authorizations list is empty as shown on the following screenshot (for the OpenId Connect Discovery security scheme):

xsrf-header-blocks-open-id-discovery

It seems that Swagger adds the X-XSRF-TOKEN to the request when asking for the openid-configuration which is not accepted by Keycloak:

Access to fetch at 'http://127.0.0.1:8024/auth/realms/Spring-Boot-Example/.well-known/openid-configuration' from origin 'http://localhost:8080' has been blocked by CORS policy: Request header field x-xsrf-token is not allowed by Access-Control-Allow-Headers in preflight response.

I want to keep the CSRF protection enabled in my Spring Boot API (and Springdoc) as I'm planning to serve frontend with Angular. Furthermore, in the The OAuth 2.0 Authorization Framework specification we can read: "The client MUST implement CSRF protection" (see https://datatracker.ietf.org/doc/html/rfc6749#section-10.12).

My main question is: Is there a way to replace the implicit flow in Swagger UI but keep CSRF protection in my API?

Related issues

The problem was already raised in other places but no solution or in-depth discussion was provided:

I hope that we can explore the issue here.

@little-pinecone
Copy link
Author

It seems that it was Springdoc issue after all. It was fixed with the #1469.

The fix is available since springdoc-openapi v1.6.6.

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

No branches or pull requests

1 participant