Skip to content

Latest commit

 

History

History
228 lines (163 loc) · 10.2 KB

jwt.md

File metadata and controls

228 lines (163 loc) · 10.2 KB

Required dependencies: io.ktor:ktor-server-auth, io.ktor:ktor-server-auth-jwt

Code examples: auth-jwt-hs256, auth-jwt-rs256

JSON Web Token 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.

You can get general information about authentication and authorization in Ktor in the section.

Add dependencies {id="add_dependencies"}

To enable JWTauthentication, you need to include the ktor-server-auth and ktor-server-auth-jwt artifacts in the build script:

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

JWT authorization flow {id="flow"}

The JWT authorization flow in Ktor might look as follows:

  1. A client makes a POST request with the credentials to a specific authentication route in a server application. The example below shows an HTTP client POST request with the credentials passed in JSON:
    {src="snippets/auth-jwt-hs256/requests.http" lines="2-8"}
  2. 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 or RS256 with a public/private key pair.
  3. A server sends a generated JWT to a client.
  4. A client can now make a request to a protected resource with a JSON web token passed in the Authorization header using the Bearer schema.
    {src="snippets/auth-jwt-hs256/requests.http" lines="13-14"}
  5. A server receives a request and performs the following validations:
  6. After validation, a server responds with the contents of a protected resource.

Install JWT {id="install"}

To install the jwt authentication provider, call the jwt function inside the install block:

install(Authentication) {
    jwt {
        // Configure jwt authentication
    }
}

You can optionally specify a provider name that can be used to authenticate a specified route.

Configure JWT {id="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 full runnable projects here: auth-jwt-hs256, auth-jwt-rs256.

Step 1: Configure JWT settings {id="jwt-settings"}

To configure JWT-related settings, you can create a custom jwt group in the application.conf configuration file. This file might look as follows:

{style="block" src="snippets/auth-jwt-hs256/src/main/resources/application.conf" lines="11-16"}

{style="block" src="snippets/auth-jwt-rs256/src/main/resources/application.conf" lines="11-16"}

Note that secret information should not be stored in the configuration file as plain text. Consider using environment variables to specify such parameters.

{type="warning"}

You can access these settings in code in the following way:

{style="block" src="snippets/auth-jwt-hs256/src/main/kotlin/com/example/Application.kt" lines="24-27"}

{style="block" src="snippets/auth-jwt-rs256/src/main/kotlin/com/example/Application.kt" lines="31-34"}

Step 2: Generate a token {id="generate"}

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:

{style="block" src="snippets/auth-jwt-hs256/src/main/kotlin/com/example/Application.kt" lines="50-61"}

{style="block" src="snippets/auth-jwt-rs256/src/main/kotlin/com/example/Application.kt" lines="58-72"}

  1. post("/login") defines an authentication route for receiving POST requests.
  2. call.receive<User>() receives user credentials sent as a JSON object and converts it to a User class object.
  3. 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.
  4. call.respond sends a token to a client as a JSON object.

Step 3: Configure realm {id="realm"}

The realm property allows you to set the realm to be passed in WWW-Authenticate header when accessing a protected route.

{style="block" src="snippets/auth-jwt-hs256/src/main/kotlin/com/example/Application.kt" lines="27-30,46-47"}

Step 4: Configure a token verifier {id="configure-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 is http://0.0.0.0:8080, so a JWKS endpoint address will be http://0.0.0.0:8080/.well-known/jwks.json.

{style="block" src="snippets/auth-jwt-hs256/src/main/kotlin/com/example/Application.kt" lines="24-35,46-47"}

{style="block" src="snippets/auth-jwt-rs256/src/main/kotlin/com/example/Application.kt" lines="32-44,55-56"}

Step 5: Validate JWT payload {id="validate-payload"}

  1. The validate function allows you to perform additional validations on the JWT payload. Check the credential parameter, which represents a JWTCredential object and contains the JWT payload. In the example below, the value of a custom username claim is checked.

    {style="block" src="snippets/auth-jwt-hs256/src/main/kotlin/com/example/Application.kt" lines="28-29,36-42,46-47"}

    In a case of successful authentication, return JWTPrincipal.

  2. The challenge function allows you to configure a response to be sent if authentication fails.

    {style="block" src="snippets/auth-jwt-hs256/src/main/kotlin/com/example/Application.kt" lines="28-29,43-47"}

Step 6: Define authorization scope {id="authenticate-route"}

After configuring the jwt provider, you can define the authorization for the different resources in our application using the authenticate function. In a 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.

{style="block" src="snippets/auth-jwt-hs256/src/main/kotlin/com/example/Application.kt" lines="49,63-71"}