Ktor using OAuth 2.0 and IdentityServer4
source link: https://www.scottbrady91.com/kotlin/ktor-using-oauth-2-and-identityserver4
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
This article will show you how to configure a Kotlin Ktor application to get access tokens from IdentityServer4 using OAuth 2.0. These tokens can then be used to access an API on behalf of a user. We’ll be using JWTs as our access tokens. To find out how to authorize access to a Ktor API using JWTs, check out my past article “JSON Web Token Verification in Ktor using Kotlin and Java-JWT”.
Ktor OAuth Support
Currently, Ktor only supports OAuth which means our Ktor application can receive access tokens to talk to an API on behalf of the user, but it cannot find out who the user is. If we wanted to find out who the user is and to receive identity tokens, we would need OpenID Connect, which is currently unsupported.
The Ktor OAuth library is hard-coded to support the authorization code flow. Ideally, in this scenario, we’d also like to use PKCE so that we can prevent authorization codes from other users being injected into our application.
Also, due to an oddity in the OAuth 2.0 specification, the Ktor basic authentication mechanism may not work with some authorization servers (basic auth is usually base64(client_id + ":" + client_secret)
but OAuth defines it as base64(urlformencode(client_id) + ":" + urlformencode(client_secret))
.
Only the query string response mode is supported, and the default value for state is random, utilizing a nonce (no more than once) generator.
Configuring OAuth in Ktor
To create my Ktor site I used the Ktor QuickStart guide, with an initial root page that displayed a button to trigger the OAuth process:
fun main(args: Array<String>) {
embeddedServer(Netty, 8080) {
routing {
get("/") {
call.respondText("""Click <a href="/oauth">here</a> to get tokens""", ContentType.Text.Html)
}
}
}.start(wait = true)
}
First, initialize your OAuth 2 client settings using OAuth2ServerSettings
:
val clientSettings = OAuthServerSettings.OAuth2ServerSettings(
name = "IdentityServer4",
authorizeUrl = "http://localhost:5000/connect/authorize", // OAuth authorization endpoint
accessTokenUrl = "http://localhost:5000/connect/token", // OAuth token endpoint
clientId = "ktor_app",
clientSecret = "super_secret",
// basic auth implementation is not "OAuth style" so falling back to post body
accessTokenRequiresBasicAuth = false,
requestMethod = HttpMethod.Post, // must POST to token endpoint
defaultScopes = listOf("api1.read", "api1.write") // what scopes to explicitly request
)
If you want to customise the authorization request with extra parameters, then you can set the authorizeUrlInterceptor
.
For example:
authorizeUrlInterceptor = { this.parameters.append("response_mode", "query")}
You then need to register the OAuth authentication (ish) provider, installing it to the application like so:
install(Authentication) {
oauth("IdentityServer4") {
client = HttpClient(Apache)
providerLookup = { clientSettings }
urlProvider = { "http://localhost:8080/oauth" }
}
}
Where “IdentityServer4” is the name of your authentication provider, client
is a HttpClient
that will handle the backchannel requests to the token endpoint, provider
is our client settings from before, and urlProvider
is the URL that will be configured to receive our authorization code.
This will also serve as the redirect_uri
value in our authorization request.
You then need to register this route while also applying your authentication provider. This serves to both start the authorization request and also handle the redirect URI and subsequent token request. At this point, we can also access the user principal. At the moment I’m just displaying it to the user (bad), while in reality, we would store the token for the user (maybe a cookie or persistent store somewhere).
routing {
// other routes
authenticate("IdentityServer4") {
get("/oauth") {
val principal = call.authentication.principal<OAuthAccessTokenResponse.OAuth2>()
call.respondText("Access Token = ${principal?.accessToken}")
}
}
}
Configuring IdentityServer4 for Ktor
There’s nothing special you need to do in IdentityServer4 for Ktor support, and you can spin up an in-memory test instance using either the dotnet new is4inmem
or following my getting started guide.
Your client configuration for Ktor should, therefore, look something like:
new Client
{
ClientId = "ktor_app",
AllowedGrantTypes = GrantTypes.Code,
AllowedScopes = {"api1.read", "api1.write"},
ClientSecrets = {new Secret("super_secret".Sha256())},
RedirectUris = {"http://localhost:8080/oauth"}
}
You can find my config file on GitHub for comparison (I’ve just requested two scopes on an imaginary API).
You should now be able to log in as bob or alice and get access tokens in your Ktor application.
Source Code
You can find the full working solution for this article on GitHub, including a test instance of IdentityServer4.
Check out the Kotlin category on my blog for more OAuth and Kotlin meddling.
Recommend
-
34
Ktor 1.3 Release
-
32
We're excited to announce the availability of Ktor 1.4.0, which also marks the first release where we've moved to Semantic Versioning. In addition to a few minor features, the release addresses a bunc
-
14
Using ECDSA in IdentityServer4 By default, IdentityServer4 uses RS256 to sign identity tokens and JWT access tokens; however, it does also support Elliptical Curve Cryptography (ECC). Using Elliptical Curve Digital Signing Algorith...
-
23
Getting Started with IdentityServer4 and Duende IdentityServer Scott Brady 22 September 2016 ・ Identity Serve...
-
3
Using Ktor Client MockEngine for Integration and UI Tests MockEngine replaces real network calls with mocked ones that use pre-defined data and status codes. The engine can be shared between Integration and UI tests.
-
17
JSON Web Token Verification in Ktor using Kotlin and Java-JWT Scott Brady 20 November 2017 ・ Kotlin ・ Updated Fe...
-
7
Releases Ktor 2.1.0 Released and it comes with goodies!
-
7
Pagination, Sorting and Custom Plugins in KtorToday in this article we will talk about how to implement Pagination, Sorting and Custom Plugins using Ktor. This is an extension of my previous article on
-
10
Ktor and SQLDelight for Kotlin Back-End Development This website stores cookies o...
-
3
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK