juhaku / utoipa

Simple, Fast, Code first and Compile time generated OpenAPI documentation for Rust
Apache License 2.0
2.23k stars 173 forks source link

Rapidoc OAuth redirect URL doesn't work #886

Open aminya opened 6 months ago

aminya commented 6 months ago

I am trying to integrate Auth0 into Rapidoc. I have added the following OAuth2 implementations, but none of them work. The issue is that Auth0 redirects back to oauth-receiver.html with the access token information, but this file doesn't exist. I tried to create this manually and serve it using actix_files, but it doesn't seem to fix the issue.

Could be related to https://github.com/rapi-doc/RapiDoc/issues/777

use utoipa::{
  openapi::{
    security::{
      AuthorizationCode, ClientCredentials, Flow, HttpAuthScheme, HttpBuilder, Implicit, OAuth2,
      Scopes, SecurityScheme,
    },
    Components,
  },
  Modify, OpenApi,
};

#[derive(OpenApi)]
#[openapi(
        paths(
            ...
        ),
        modifiers(&SecurityAddon)
    )]
pub struct ApiDoc;

struct SecurityAddon;

impl Modify for SecurityAddon {
  fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
    if openapi.components.is_none() {
      openapi.components = Some(Components::new());
    }

    let auth0_domain = std::env::var("AUTH0_DOMAIN").unwrap();
    let api_audience = std::env::var("AUTH0_AUDIENCE").unwrap();

    openapi.components.as_mut().unwrap().add_security_scheme(
      "bearerAuth",
      SecurityScheme::OAuth2(OAuth2::new([
        Flow::Implicit(Implicit::new(
          format!("{auth0_domain}/authorize?audience={api_audience}",),
          Scopes::from_iter([
            ("openid", "OpenId"),
            ("profile", "Profile"),
            ("email", "Email"),
          ]),
        )),
        Flow::AuthorizationCode(AuthorizationCode::new(
          format!("{auth0_domain}/authorize?audience={api_audience}",),
          format!("{auth0_domain}/oauth/token"),
          Scopes::from_iter([
            ("openid", "OpenId"),
            ("profile", "Profile"),
            ("email", "Email"),
          ]),
        )),
      ])),
    );
  }
}

And in my services, I have:

app.service(RapiDoc::with_openapi("/api/v1/openapi2.json", openapi.clone()).path("/docs"))
juhaku commented 3 weeks ago

For utoia-rapidocs side there's not much to what comes to the implementation to serve the RapiDoc and OpenAPI spec via actix-web it literally implements HttpServiceFactory which is attached by end users to the App::new().service(...) The implementation literally looks like this:

impl HttpServiceFactory for RapiDoc {
    fn register(self, config: &mut actix_web::dev::AppService) {
        let html = self.to_html();

        async fn serve_rapidoc(rapidoc: Data<String>) -> impl Responder {
            HttpResponse::Ok()
                .content_type("text/html")
                .body(rapidoc.to_string())
        }

        Resource::new(self.path.as_ref()) // < -- serve the rapidocs html file from `path`,  `RapiDoc` default to /.
            .guard(Get())
            .app_data(Data::new(html))
            .to(serve_rapidoc)
            .register(config);

        if let Some(openapi) = self.openapi { // If there is openapi serve the openapi according to the spec url
            async fn serve_openapi(openapi: Data<String>) -> impl Responder {
                HttpResponse::Ok()
                    .content_type("application/json")
                    .body(openapi.into_inner().to_string())
            }

            Resource::new(self.spec_url.as_ref())
                .guard(Get())
                .app_data(Data::new(
                    openapi.to_json().expect("Should serialize to JSON"),
                ))
                .to(serve_openapi)
                .register(config);
        }
    }
}

So my rough guess is the something is not configured correctly for the request is not pointing to or returning to correct place.

Note! The paths for RapiDoc is defined from the root (/) of the application always. So adding RapiDoc under some scoped service might cause some issues. And the paths need to be checked manually.