http4k icon indicating copy to clipboard operation
http4k copied to clipboard

Implementing stateless OAuth

Open vojkny opened this issue 5 years ago • 2 comments

I am going through the OAuth examples here:

  • https://www.http4k.org/guide/modules/oauth/
  • https://www.http4k.org/cookbook/custom_oauth_provider/

But both of them propose implementation as a filter, meaning they protect certain resource. Both examples somehow store the fact that the user is logged (cookie/header) and let them visit the resource.

Instead of redirecting to the protected resource (in this case /), I would like to directly respond with custom JSON response immediately after the token callback. This response requires working with the authorization callback response, so it is too late to build it after the redirect back to the / where the authorization started.

Is this feasible with the oauth module?

vojkny avatar Jun 09 '20 21:06 vojkny

I am assuming you are referring to a callback from an implicit grant or an authorization code grant. I had to do something similar in the past but it honestly felt really awkward.

What I did was implementing a custom OAuthPersistence similar to the InsecureCookieBasedOAuthPersistence. However instead of storing the access token, I overwrote the retrieveToken and assignToken methods similar to this:

override fun retrieveToken(request: Request): AccessToken? = request.header("Authorization")?.removePrefix("Bearer ")?.let(::AccessToken)

override fun assignToken(request: Request, redirect: Response, accessToken: AccessToken): Response {
  val internalAuthorizedRequest = Request(GET, Header.LOCATION(redirect)).header("Authorization", "Bearer ${accessToken.value}")
  return getApp()(internalAuthorizedRequest)
            .invalidateCookie(csrfName)
            .invalidateCookie(nonceName)
}

getApp is a function () -> HttpHandler passed into the OAuthPersistence that gives me the protected app with the authFilter in front. getApp has to be a function to deal with the fact that OAuthPersistence has to be created before the OAuthProvider is created:

    var authProvider: OAuthProvider? = null
    val protectedApp: HttpHandler by lazy { authProvider!!.authFilter.then(/** add your app here **/) })
    authProvider = OAuthProvider.gitHub(
        ..., 
        oAuthPersistence = StatelessOAuthPersistence( getApp = { protectedApp }
    )

    val appAndOAuthCallback: HttpHandler = routes(
            authProvider.callbackEndpoint,
            "/" bind GET to protectedApp,
            "/{:.*}" bind GET to protectedApp)
    appAndOAuthCallback.asServer(Jetty(8080)).start()

At least two problems with this solution:

  1. It currently only works with GET requests to the protected resource. Any POST body or custom headers would get lost due to the redirect anyways.
  2. I haven't found a way to get access to the idToken yet (only relevant with OpenID Connect)

There are probably lots of other issues with this approach :wink:

nlochschmidt avatar Aug 20 '20 23:08 nlochschmidt

@knyttl do you want to return the resource as part of the authorization callback? Or the access token request?

Either way, I believe that goes against the standard which tries to decouple authorization server from the actual resource. But happy to consider if that's allowed by the standard (that's a new one for me).

s4nchez avatar Aug 22 '20 16:08 s4nchez