SMILEY4 / ktor-swagger-ui

Kotlin Ktor plugin to generate OpenAPI and provide Swagger UI
Apache License 2.0
157 stars 25 forks source link

Unable to get oauth2 authorizationCode flow to work #2

Closed MarcelBochtler closed 2 years ago

MarcelBochtler commented 2 years ago

I'm unable to get authentication to work using the oauth2 authorizationCode flow. In Insomnia I'm configuring the authorization with the following parameters: image

In my application, I tried to configure the Swagger UI like this for a local KeyCloak:

install(SwaggerUI) {
    defaultSecuritySchemeName = "token"
    defaultUnauthorizedResponse {
        description = "Token is invalid."
    }
    securityScheme("token") {
        type = AuthType.OAUTH2
        flows {
            authorizationCode {
                authorizationUrl = "http://localhost:8081/realms/master/protocol/openid-connect/auth"
                tokenUrl = "http://localhost:8081/realms/master/protocol/openid-connect/token"
            }
        }
    }
}

The routing is configured like this:

routing {
    route("api/v1") {
        authenticate("token") {
            get("hello") { // From io.github.smiley4.ktorswaggerui.dsl.get
                call.respond("Hello Auth.")
            }
        }
    }
}

The SwaggerUI shows the endpoint correctly as authenticated. However, sending the request in SwaggerUI, does not open an authentication window, and results in 401.

I tried your Authentication example from here which was working, but for my application I do require Oauth2 authentication.

Can you give me a hint what I'm doing wrong?

SMILEY4 commented 2 years ago

Hi,

unfortunatly i dont know too much about oath-flows and keycloak, so i might not be of any help with specific configurations.

However, i just found (and fixed) a bug where the security scheme was not added to the final openapi-spec at all - this might be the reason it doesnt work for you (it definitely doesnt help).

Could you try version "0.5.1" and see if that fixed it for you?

MarcelBochtler commented 2 years ago

Version 0.5.1 does help partially. I now get the Authorize button in the SwaggerUI. Unfortunately, the actual authentication is still not working.

After clicking on Authorize and entering my client-id a new tab opens: localhost:8081/realms/master/protocol/openid-connect/auth?response_type=code&client_id=ort-server&redirect-uri=http%3A%2F%2Flocalhost%3A8080%2Fswagger-ui%2Foauth2-redirect.html&state=VGh1IFNlcCAxNSAyMDIyIDA4OjU2OjEzIEdNVCswMjAwIChDZW50cmFsIEV1cm9wZWFuIFN1bW1lciBUaW1lKQ%3D%3D This new tab then the Keycloak error message: Invalid parameter: redirect_uri.

When manually changing redirect_uri to redirect-uri in the URL, I get the username and password prompt. Entering it forwards me to Keycloak instead of redirecting back to Swagger. The actual token in Swagger is also not set afterwards, so authenticated requests still fail.

SMILEY4 commented 2 years ago

Regarding the Invalid parameter: redirect_uri-error. I think this error occurs if you have not added the url you want to redirect to as a valid redirect uri. You could try and set the "Redirect Url" in your first screenshot to "*" (for testing purposes only!!!) - this at least fixed that problem for me and swagger can receive a valid token, which it also sends on sucessive requests to the ktor-api. Im currently still having trouble with ktor ignoring that token and still responding with the keycloak-login page. I feel like this is because im missunderstanding/missing something about oauth here, since nothing here is validating the provided token and this needs to be done by another ktor-Authentication ?

This is my current test-setup:

fun main() {
    embeddedServer(Netty, port = 8080, host = "localhost") {

        val keycloakOAuth = "keycloakAuth"

        val keycloakAddress = "http://localhost:8081"

        val keycloakProvider = OAuthServerSettings.OAuth2ServerSettings(
            name = "keycloak",
            authorizeUrl = "$keycloakAddress/realms/myrealm/protocol/openid-connect/auth",
            accessTokenUrl = "$keycloakAddress/realms/myrealm/protocol/openid-connect/token",
            clientId = "myclient",
            clientSecret = "...",
            accessTokenRequiresBasicAuth = false,
            requestMethod = HttpMethod.Post,
            defaultScopes = listOf("roles")
        )

        install(Authentication) {
            oauth(keycloakOAuth) {
                client = HttpClient(Apache)
                providerLookup = { keycloakProvider }
                urlProvider = { "http://localhost:8080/oauth" }
            }
        }

        install(SwaggerUI) {
            defaultSecuritySchemeName = keycloakOAuth
            securityScheme(keycloakOAuth) {
                type = AuthType.OAUTH2
                flows {
                    authorizationCode {
                        authorizationUrl = keycloakProvider.authorizeUrl
                        tokenUrl = keycloakProvider.accessTokenUrl
                    }
                }
            }
        }

        routing {
            authenticate(keycloakOAuth) {
                get("/oauth", {}) {
                    val principal = call.authentication.principal<OAuthAccessTokenResponse.OAuth2>()
                    call.respondText("Access Token = ${principal?.accessToken}")
                }
                get("hello", {}) {
                    call.respondText("Hello World!")
                }
            }
        }

    }.start(wait = true)
}

After a successfull authorization in swagger-ui, swagger tries to call the /hello endpoint with the following curl

curl -X 'GET' \
  'http://localhost:8080/hello' \
  -H 'accept: */*' \
  -H 'Authorization: Bearer eyJhbGc...'
MarcelBochtler commented 2 years ago

Yes, you're right. I had to correctly configure the redirect URL in Keycloak to get it working. Thanks for your help.