JSON Web Tokens
JSON Web Token (JWT) is an open standard that defines a way for securely transmitting information between parties as a JSON object. This information can be verified and trusted since it is signed using a shared secret (with the HS256
algorithm) or a public/private key pair (for example, RS256
).
Ktor handles JWTs passed in the Authorization
header using the Bearer
schema and allows you to:
verify the signature of a JSON web token;
perform additional validations on the JWT payload.
Add dependencies
To enable JWT
authentication, you need to include the ktor-server-auth
and ktor-server-auth-jwt
artifacts in the build script:
JWT authorization flow
The JWT authorization flow in Ktor might look as follows:
A client makes a
POST
request with the credentials to a specific authentication route in a server application. The example below shows an HTTP clientPOST
request with the credentials passed in JSON:POST http://localhost:8080/login Content-Type: application/json { "username": "jetbrains", "password": "foobar" }If the credentials are valid, a server generates a JSON web token and signs it with the specified algorithm. For example, this might be
HS256
with a specific shared secret orRS256
with a public/private key pair.A server sends a generated JWT to a client.
A client can now make a request to a protected resource with a JSON web token passed in the
Authorization
header using theBearer
schema.GET http://localhost:8080/hello Authorization: Bearer {{auth_token}}A server receives a request and performs the following validations:
Verifies a token's signature. Note that a verification way depends on the algorithm used to sign a token.
Perform additional validations on the JWT payload.
After validation, a server responds with the contents of a protected resource.
Install JWT
To install the jwt
authentication provider, call the jwt function inside the install
block:
You can optionally specify a provider name that can be used to authenticate a specified route.
Configure JWT
In this section, we'll see how to use JSON web tokens in a server Ktor application. We'll demonstrate two approaches to signing tokens since they require slightly different ways to verify tokens:
Using
HS256
with a specified shared secret.Using
RS256
with a public/private key pair.
You can find complete projects here: auth-jwt-hs256, auth-jwt-rs256.
Step 1: Configure JWT settings
To configure JWT-related settings, you can create a custom jwt
group in a configuration file. For example, the application.conf
file might look as follows:
You can access these settings in code in the following way:
Step 2: Generate a token
To generate a JSON web token, you can use JWTCreator.Builder. Code snippets below show how to do this for both HS256
and RS256
algorithms:
post("/login")
defines an authentication route for receivingPOST
requests.call.receive<User>()
receives user credentials sent as a JSON object and converts it to aUser
class object.JWT.create()
generates a token with the specified JWT settings, adds a custom claim with a received username, and signs a token with the specified algorithm:For
HS256
, a shared secret is used to sign a token.For
RS256
, a public/private key pair is used.
call.respond
sends a token to a client as a JSON object.
Step 3: Configure realm
The realm
property allows you to set the realm to be passed in the WWW-Authenticate
header when accessing a protected route.
Step 4: Configure a token verifier
The verifier
function allows you to verify a token format and its signature:
For
HS256
, you need to pass a JWTVerifier instance to verify a token.For
RS256
, you need to pass JwkProvider, which specifies a JWKS endpoint for accessing a public key used to verify a token. In our case, an issuer ishttp://0.0.0.0:8080
, so a JWKS endpoint address will behttp://0.0.0.0:8080/.well-known/jwks.json
.
Step 5: Validate JWT payload
The
validate
function allows you to perform additional validations on the JWT payload. Check thecredential
parameter, which represents a JWTCredential object and contains the JWT payload. In the example below, the value of a customusername
claim is checked.install(Authentication) { jwt("auth-jwt") { validate { credential -> if (credential.payload.getClaim("username").asString() != "") { JWTPrincipal(credential.payload) } else { null } } } }In the case of successful authentication, return JWTPrincipal.
The
challenge
function allows you to configure a response to be sent if authentication fails.install(Authentication) { jwt("auth-jwt") { challenge { defaultScheme, realm -> call.respond(HttpStatusCode.Unauthorized, "Token is not valid or has expired") } } }
Step 6: Protect specific resources
After configuring the jwt
provider, you can protect specific resources in our application using the authenticate function. In the case of successful authentication, you can retrieve an authenticated JWTPrincipal inside a route handler using the call.principal
function and get the JWT payload. In the example below, the value of a custom username
claim and a token expiration time are retrieved.