Use a custom OAuth provider
http4k Core
It is very easy to configure http4k to integrate with any OAuth2 provider who supports the Authorisation Code Grant.
Gradle setup
dependencies {
implementation(platform("org.http4k:http4k-bom:5.37.1.1"))
implementation("org.http4k:http4k-core")
implementation("org.http4k:http4k-security-oauth")
}
For this example, simply reconfigure the OAuthProvider
instance with the correct details, and provide custom logic for persisting and retrieving the CSRF and AccessToken.
Code
package content.howto.use_a_custom_oauth_provider
import org.http4k.client.ApacheClient
import org.http4k.core.Credentials
import org.http4k.core.HttpHandler
import org.http4k.core.Method.GET
import org.http4k.core.Request
import org.http4k.core.Response
import org.http4k.core.Status.Companion.OK
import org.http4k.core.Uri
import org.http4k.core.then
import org.http4k.filter.ServerFilters
import org.http4k.routing.bind
import org.http4k.routing.routes
import org.http4k.security.AccessToken
import org.http4k.security.CrossSiteRequestForgeryToken
import org.http4k.security.Nonce
import org.http4k.security.OAuthPersistence
import org.http4k.security.OAuthProvider
import org.http4k.security.OAuthProviderConfig
import org.http4k.security.PkceChallengeAndVerifier
import org.http4k.security.openid.IdToken
import org.http4k.server.SunHttp
import org.http4k.server.asServer
// this example shows how to configure a custom provider for the OAuth2 Auth Code Grant flow
fun main() {
val port = 9000
// the callback uri which is configured in our OAuth provider
val callbackUri = Uri.of("http://localhost:$port/callback")
// custom OAuth2 provider configuration
val oauthProvider = OAuthProvider(
OAuthProviderConfig(
authBase = Uri.of("https://auth.chatroulette.com"),
authPath = "/oauth2/auth", tokenPath = "/oauth2/token",
credentials = Credentials("username", "somepassword"),
apiBase = Uri.of("https://api.chatroulette.com")
),
ApacheClient(),
callbackUri,
listOf("emailScope", "nameScope", "familyScope"),
CustomOAuthPersistence()
)
val app: HttpHandler =
routes(
callbackUri.path bind GET to oauthProvider.callback,
"/" bind GET to oauthProvider.authFilter.then { Response(OK).body("hello!") }
)
ServerFilters.CatchAll()
.then(app)
.asServer(SunHttp(port)).start().block()
}
// This interface allows us to provide custom logic for storing and
// verifying the CSRF and AccessTokens.
//
// To be maximally secure, never let the end-user see the access token!
//
// Also avoid allowing third parties set the original uri as this might
// allow phishing attacks.
//
// One strategy might be to use an enum to map to a set of know uris
// e.g. shoppingCart -> /cart
class CustomOAuthPersistence : OAuthPersistence {
private var nonce: Nonce? = null
private var csrf: CrossSiteRequestForgeryToken? = null
private var accessToken: AccessToken? = null
private var originalUri: Uri? = null
private var pkce: PkceChallengeAndVerifier? = null
override fun retrieveCsrf(request: Request): CrossSiteRequestForgeryToken? = csrf
override fun assignCsrf(redirect: Response, csrf: CrossSiteRequestForgeryToken): Response {
this.csrf = csrf
return redirect.header("action", "assignCsrf")
}
override fun assignNonce(redirect: Response, nonce: Nonce): Response {
this.nonce = nonce
return redirect.header("action", "assignNonce")
}
override fun retrieveNonce(request: Request): Nonce? = nonce
override fun assignOriginalUri(redirect: Response, originalUri: Uri): Response {
this.originalUri = originalUri
return redirect.header("action", "assignOriginalUri")
}
override fun retrieveOriginalUri(request: Request): Uri? = originalUri
override fun assignPkce(redirect: Response, pkce: PkceChallengeAndVerifier): Response {
this.pkce = pkce
return redirect.header("action", "assignPkce")
}
override fun retrievePkce(request: Request): PkceChallengeAndVerifier? = pkce
override fun retrieveToken(request: Request): AccessToken? = accessToken
override fun assignToken(
request: Request,
redirect: Response,
accessToken: AccessToken,
idToken: IdToken?
): Response {
this.accessToken = accessToken
return redirect.header("action", "assignToken")
}
}