Ktor 2.3.12 Help

Authentication and authorization

Ktor provides the Authentication plugin to handle authentication and authorization. Typical usage scenarios include logging in users, granting access to specific resources, and securely transmitting information between parties. You can also use Authentication with Sessions to keep a user's information between requests.

Supported authentication types

Ktor supports the following authentication and authorization schemes:

HTTP authentication

HTTP provides a general framework for access control and authentication. In Ktor, you can use the following HTTP authentication schemes:

  • Basic - uses Base64 encoding to provide a username and password. Generally is not recommended if not used in combination with HTTPS.

  • Digest - an authentication method that communicates user credentials in an encrypted form by applying a hash function to the username and password.

  • Bearer - an authentication scheme that involves security tokens called bearer tokens. The Bearer authentication scheme is used as part of OAuth or JWT, but you can also provide custom logic for authorizing bearer tokens.

Form-based authentication

Form-based authentication uses a web form to collect credential information and authenticate a user.

JSON Web Tokens (JWT)

JSON Web Token is an open standard for securely transmitting information between parties as a JSON object. You can use JSON Web Tokens for authorization: when the user is logged in, each request will include a token, allowing the user to access resources that are permitted with that token. In Ktor, you can verify a token and validate the claims contained within it using the jwt authentication.

LDAP

LDAP is an open and cross-platform protocol used for directory services authentication. Ktor provides the ldapAuthenticate function to authenticate user credentials against a specified LDAP server.

OAuth

OAuth is an open standard for securing access to APIs. The oauth provider in Ktor allows you to implement authentication using external providers such as Google, Facebook, Twitter, and so on.

Session

Sessions provide a mechanism to persist data between different HTTP requests. Typical use cases include storing a logged-in user's ID, the contents of a shopping basket, or keeping user preferences on the client. In Ktor, a user that already has an associated session can be authenticated using the session provider. Learn how to do this from Session authentication.

Custom

Ktor also provides an API for creating custom plugins, which can be used to implement your own plugin for handling authentication and authorization. For example, the AuthenticationChecked hook is executed after authentication credentials are checked, and it allows you to implement authorization: custom-plugin-authorization.

Add dependencies

To use Authentication, you need to include the ktor-server-auth artifact in the build script:

implementation("io.ktor:ktor-server-auth:$ktor_version")
implementation "io.ktor:ktor-server-auth:$ktor_version"
<dependency> <groupId>io.ktor</groupId> <artifactId>ktor-server-auth-jvm</artifactId> <version>${ktor_version}</version> </dependency>

Note that some authentication providers, such as JWT and LDAP, require additional artifacts.

Install Authentication

To install the Authentication plugin to the application, pass it to the install function in the specified module. The code snippets below show how to install Authentication...

  • ... inside the embeddedServer function call.

  • ... inside the explicitly defined module, which is an extension function of the Application class.

import io.ktor.server.engine.* import io.ktor.server.netty.* import io.ktor.server.application.* import io.ktor.server.auth.* fun main() { embeddedServer(Netty, port = 8080) { install(Authentication) // ... }.start(wait = true) }
import io.ktor.server.application.* import io.ktor.server.auth.* // ... fun Application.module() { install(Authentication) // ... }

Configure Authentication

After installing Authentication, you can configure and use Authentication as follows:

Step 1: Choose an authentication provider

To use a specific authentication provider (basic, digest, form, and so on), you need to call the corresponding function inside the install block. For example, to use the basic authentication, call the basic function:

import io.ktor.server.application.* import io.ktor.server.auth.* // ... install(Authentication) { basic { // [[[Configure basic authentication|server-basic-auth.html]]] } }

Inside this function, you can configure settings specific to this provider.

Step 2: Specify a provider name

A function for using a specific provider optionally allows you to specify a provider name. A code sample below installs the basic and form providers with the auth-basic and auth-form names, respectively:

install(Authentication) { basic("auth-basic") { // [[[Configure basic authentication|server-basic-auth.html]]] } form("auth-form") { // [[[Configure form authentication|server-form-based-auth.html]]] } // ... }

These names can be used later to authenticate different routes using different providers.

Step 3: Configure a provider

Each provider type has its own configuration. For instance, the BasicAuthenticationProvider.Config class contains options passed to the basic function. The most important function exposed by this class is validate that validates a username and password. A code sample below shows how it can look:

install(Authentication) { basic("auth-basic") { realm = "Access to the '/' path" validate { credentials -> if (credentials.name == "jetbrains" && credentials.password == "foobar") { UserIdPrincipal(credentials.name) } else { null } } } }

To understand how the validate function works, we need to introduce two terms:

  • A principal is an entity that can be authenticated: a user, a computer, a service, etc. In Ktor, various authentication providers might use different principals. For example, the basic, digest, and form providers authenticate UserIdPrincipal while the jwt provider verifies JWTPrincipal.

  • A credential is a set of properties for a server to authenticate a principal: a user/password pair, an API key, and so on. For instance, the basic and form providers use UserPasswordCredential to validate a username and password while jwt validates JWTCredential.

So, the validate function checks a specified Credential and returns a Principal in the case of successful authentication or null if authentication fails.

Step 4: Protect specific resources

The final step is to protect specific resources in our application. You can do this by using the authenticate function. This function accepts two optional parameters:

  • A name of a provider used to authenticate nested routes. The code snippet below uses a provider with the auth-basic name to protect the /login and /orders routes:

    routing { authenticate("auth-basic") { get("/login") { // ... } get("/orders") { // ... } } get("/") { // ... } }
  • A strategy used to resolve nested authentication providers. This strategy is represented by the AuthenticationStrategy enumeration value. For instance, the client should provide authentication data for all providers registered with the AuthenticationStrategy.Required strategy. In the code snippet below, only a user that passed session authentication can try to access the /admin route using basic authentication:

    routing { authenticate("auth-session", strategy = AuthenticationStrategy.Required) { get("/hello") { // ... } authenticate("auth-basic", strategy = AuthenticationStrategy.Required) { get("/admin") { // ... } } } }

    You can find the full example here: auth-form-session-nested.

Step 5: Get a principal inside a route handler

In the case of successful authentication, you can retrieve an authenticated Principal inside a route handler using the call.principal function. This function accepts a specific principal type returned by the configured authentication provider. In a code sample below, call.principal is used to obtain UserIdPrincipal and get a name of an authenticated user.

routing { authenticate("auth-basic") { get("/") { call.respondText("Hello, ${call.principal<UserIdPrincipal>()?.name}!") } } }

If you use session authentication, a principal might be a data class that stores session data. So, you need to pass this data class to call.principal:

authenticate("auth-session") { get("/hello") { val userSession = call.principal<UserSession>() } }

In the case of nested authentication providers, you can pass a provider name to call.principal to get a principal for the desired provider. In the example below, the auth-session value is passed to get a principal for a topmost session provider:

authenticate("auth-session", strategy = AuthenticationStrategy.Required) { authenticate("auth-basic", strategy = AuthenticationStrategy.Required) { get("/admin") { val userSession = call.principal<UserSession>("auth-session") } } }
Last modified: 02 April 2024