Type-safe authentication
Ktor provides a type-safe authentication scheme API that binds an authentication scheme to a principal type. You create a scheme once, pass that scheme to a route, and read the principal without a cast or a null check.
Add dependencies
To use typed authentication, add the ktor-server-auth artifact to your build script:
If using the jwt scheme, add the ktor-server-auth-jwt artifact:
If using the apiKey scheme, add the ktor-server-auth-api-key artifact:
Enable the API
The API is marked with @ExperimentalKtorApi, so you must opt in:
The route builders use Kotlin context parameters. Kotlin 2.4.0 enables context parameters by default. With Kotlin 2.2.x or 2.3.x, enable them in your build script:
Define a principal
A principal represents the identity or other information that your application associates with an authenticated caller. The principal type must be non-null.
Create a scheme
Each authentication method provides a factory function that takes a principal type, a name, and a configuration block. The validate {} block returns your principal type, or null when the credentials are rejected:
The factory returns a value. Store it and pass it to the routes that require it.
Factory | Artifact | Notes |
|---|---|---|
|
| |
|
| JVM only. See Digest authentication in Ktor Server |
|
| |
|
| |
|
| |
|
| |
|
| JVM only. See JSON Web Tokens |
|
| See OAuth 2.0 flows |
Scheme names must be unique. If you create two different schemes with the same name, the second one fails when a route tries to use it.
Protect routes
To protect routes, pass a scheme to the authenticateWith() function. Inside the block, call.principal has the principal type defined by the scheme and is guaranteed to be non-null:
Make authentication optional
Use the authenticateWithOptional() function when a route should serve both signed-in and anonymous callers. Inside the block, use call.principalOrNull to access the principal:
A request without credentials succeeds and leaves call.principalOrNull as null. A request with invalid credentials still fails.
Accept multiple schemes
Use the authenticateWithAnyOf() function to accept more than one scheme on the same route. Ktor tries the schemes in the order you list them, and the first one that succeeds provides the principal.
All schemes must produce a principal that fits a common type, which you declare on the call:
Allow anonymous callers
Use the orAnonymous() function to build a scheme that serves callers without credentials. A request without credentials gets the principal your block returns. A request with invalid credentials still fails.
The result is a scheme whose principal type is a common supertype of the two:
Transform a principal
Use the mapPrincipal() function to convert the principal into another type, for example, by loading a user record from your database:
The transform runs only after the scheme has produced a principal. If the transform returns null, the request is rejected with 401 Unauthorized.
Check roles
Role checks are opt-in. Define a role type that implements AuthenticationRole, then build a role-aware scheme with withRoles():
The withRoles {} block runs on every request, after authentication succeeds. Use it to load roles from a database, a cache, or the principal itself.
Declare the roles a route requires with the roles parameter:
A caller who authenticates but lacks a required role receives a 403 Forbidden. A caller must have every role in the set, not just one of them.
Pass roles = null to resolve roles without requiring any. This is useful when the handler decides for itself:
The roles property exists only inside a role-aware route. On a route protected by a plain scheme, it does not compile.
Handle failures
Two things can go wrong, and they are handled separately:
onUnauthorizedruns when authentication fails. The default response is401 Unauthorized.onForbiddenruns when authentication succeeds but the caller lacks a required role. The default response is403 Forbidden.
Set a handler on the scheme to cover every route that uses it:
Or set a handler on a single route, which overrides the scheme-level one:
Ktor looks for an unauthorized handler in the following order:
The handler passed to
authenticateWith().The
onUnauthorizedhandler configured on the scheme.The provider's default challenge.
If none of them responds, the request fails with 401 Unauthorized.
For authenticateWithAnyOf(), the handler receives a map of scheme names to failure causes, so you can report why each one failed:
Combine with the classic API
Both authentication APIs can be used in the same application. You can nest a type-safe route inside a route protected by the named provider API, or nest a provider-based route inside a type-safe route:
Nested authentication layers are applied in order.
Limitations
The API is experimental. It may change in a minor release.
The API uses Kotlin context parameters, which require Kotlin 2.4.0 or the
-Xcontext-parameterscompiler option.The
authenticateWithAnyOf()function provides onlycall.principal. Scheme-specific properties such ascall.sessionare not available inside it.There is no type-safe equivalent of the LDAP provider. Call
ldapAuthenticate()inside a typedvalidate {}block instead.