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 JWTauthentication, 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
POSTrequest with the credentials to a specific authentication route in a server application. The example below shows an HTTP clientPOSTrequest 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
HS256with a specific shared secret orRS256with 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
Authorizationheader using theBearerschema.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
HS256with a specified shared secret.Using
RS256with 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 receivingPOSTrequests.call.receive<User>()receives user credentials sent as a JSON object and converts it to aUserclass 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.respondsends 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
validatefunction allows you to perform additional validations on the JWT payload. Check thecredentialparameter, which represents a JWTCredential object and contains the JWT payload. In the example below, the value of a customusernameclaim 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
challengefunction 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.