• Stars
    star
    440
  • Rank 99,050 (Top 2 %)
  • Language
    Scala
  • License
    Apache License 2.0
  • Created over 9 years ago
  • Updated about 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Web & mobile client-side akka-http sessions, with optional JWT support

akka-http-session

Build Status Join the chat at https://gitter.im/softwaremill/akka-http-session Maven Central

akka-http is an Akka module, originating from spray.io, for building reactive REST services with an elegant DSL. pekko-http is an open-source fork of akka-http.

akka-http is a great toolkit for building backends for single-page or mobile applications. In almost all apps there is a need to maintain user sessions, make sure session data is secure and cannot be tampered with.

akka-http-session provides directives for client-side session management in web and mobile applications, using cookies or custom headers + local storage, with optional Json Web Tokens format support.

A comprehensive FAQ is available, along with code examples (in Java, but easy to translate to Scala) which answers many common questions on how sessions work, how to secure them and implement using akka-http.

Each akka-http-session module for akka-http has a corresponding pekko-http module, with a different group id (see bottom of the readme).

What is a session?

Session data typically contains at least the id or username of the logged in user. This id must be secured so that a session cannot be "stolen" or forged easily.

Sessions can be stored on the server, either in-memory or in a database, with the session id sent to the client, or entirely on the client in a serialized format. The former approach requires sticky sessions or additional shared storage, while using the latter (which is supported by this library) sessions can be easily deserialized on any server.

A session is a string token which is sent to the client and should be sent back to the server on every request.

To prevent forging, serialized session data is signed using a server secret. The signature is appended to the session data that is sent to the client, and verified when the session token is received back.

akka-http-session features

  • type-safe client-side sessions
  • sessions can be encrypted
  • sessions contain an expiry date
  • cookie or custom header transport
  • support for JWT
  • refresh token support (e.g. to implement "remember me")
  • CSRF tokens support
  • Java & Scala APIs

Example

You can try out a simple example by running com.softwaremill.example.ScalaExample or com.softwaremill.example.JavaExample and opening http://localhost:8080.

SessionManager & configuration

All directives require an (implicit for scala) instance of a SessionManager[T] (or SessionManager<T>), which can be created by providing a server secret (via a SessionConfig). The secret should be a long, random string unique to each environment your app is running in. You can generate one with SessionUtil.randomServerSecret(). Note that when you change the secret, all sessions will become invalid.

A SessionConfig instance can be created using Typesafe config. The only value that you need to provide is akka.http.session.server-secret, preferably via application.conf (then you can safely call SessionConfig.fromConfig) or by using SessionConfig.default().

You can customize any of the default config options either by modifying them through application.conf or by modifying the SessionConfig case class. If a value has type Option[], you can set it to None by using a none value in the config file (for both java and scala).

When using cookies, by default the secure attribute of cookies is not set (for development), however it is recommended that all sites use https and all cookies have this attribute set.

Client-side sessions

All session-related directives take at least two parameters:

  • session continuity: oneOff vs refreshable; specifies what should happen when the session expires. If refreshable and a refresh token is present, the session will be re-created. See below for details.
  • session transport: usingCookies vs usingHeaders

Typically, you would create aliases for the session-related directives which use the right parameters basing on the current request and logic specific to your application.

Cookies vs header

Session data can be sent to the client using cookies or custom headers. The first approach is the simplest to use, as cookies are automatically sent to the server on each request.

However, cookies have some security vulnerabilities, and are typically not used in mobile applications. For these scenarios, session data can be transported using custom headers (the names of the headers are configurable in the config).

When using headers, you need to store the session (and, if used, refresh-) tokens yourself. These tokens can be stored in-memory, or persistently e.g. using the browser's local storage.

You can dynamically decide which transport to use, basing e.g. on the user-agent or other request properties.

Basic usage

Sessions are typed. The T type parameter in SessionManager[T] (or SessionManager<T>) determines what data is stored in the session. Basic types like String, Int, Long, Float, Double and Map[String, String] (Map<String, String>) are supported out-of-the box. Support for other types can be added by providing a (an implicit for scala) SessionSerializer[T, String] (SessionSerializer<T, String>). For case classes, it's most convenient to use a MultiValueSessionSerializer[T] or (MultiValueSessionSerializer<T>) which should convert the instance into a String -> String map (nested types are not supported on purpose, as session data should be small & simple). Examples of SessionSerializer and MultiValueSessionSerializer usage can be found here for scala and here for java.

Here are code samples in scala and java illustrating how to create a session manager where the session content will be a single Long number.

The basic directives enable you to set, read and invalidate the session. To create a new client-side session (create and set a new session cookie), you need to use the setSession directive. See how it's done in java and scala.

Note that when using cookies, their size is limited to 4KB, so you shouldn't put too much data in there (the signature takes about 50 characters).

You can require a session to be present, optionally require a session or get a full description of possible session decode outcomes. Check java and scala examples for details.

If a required session is not present, by default a 403 HTTP status code is returned. Finally, a session can be invalidated. See how it's done in examples for java and scala.

Encrypting the session

It is possible to encrypt the session data by modifying the akka.http.session.encrypt-data config option. When sessions are encrypted, it's not possible to read their content on the client side.

The key used for encrypting will be calculated basing on the server secret.

Session expiry/timeout

By default, sessions expire after a week. This can be disabled or changed with the akka.http.session.max-age config option.

Note that when using cookies, even though the cookie sent will be a session cookie, it is possible that the client will have the browser open for a very long time, uses Chrome or FF, or if an attacker steals the cookie, it can be re-used. Hence having an expiry date for sessions is highly recommended.

JWT: encoding sessions

By default, sessions are encoded into a string using a custom format, where expiry/data/signature parts are separated using -, and data fields are separated using = and url-encoded.

You can also encode sessions in the Json Web Tokens format, by adding the additional jwt dependency, which makes use of json4s.

When using JWT, you need to provide a serializer which serializes session data to a JValue instead of a String. A number of serializers for the basic types are present in JValueSessionSerializer, as well as a generic serializer for case classes (used above).

You may also find it helpful to include the json4s-ext library which provides serializers for common Java types such as java.util.UUID, org.joda.time._ and Java enumerations.

Grab some java and scala examples.

There are many tools available to read JWT session data using various platforms, e.g. for Angular.

It is also possible to customize the session data content generated by overriding appropriate methods in JwtSessionEncoder (e.g. provide additional claims in the payload).

Registered JWT claims support

This library supports all registered claims mentioned in RFC 7519, Section 4.1.

Static claims such as iss (Issuer), sub (Subject) and aud (Audience) can be configured by setting akka.http.session.jwt.iss, akka.http.session.jwt.sub and akka.http.session.jwt.aud string properties, respectively.

Because claims such as exp (Expiration Time) and nbf (Not Before) depend on the time at which the JWT was issued, configuration expects durations instead of fixed timestamps. Effective claim values are then calculated by adding these offsets to the current timestamp (the JWT's issue time). The offset values are configured via keys defined under akka.http.session.jwt.exp-timeout and akka.http.session.jwt.nbf-offset.

If exp-timeout is not defined, value of akka.http.session.max-age would be used instead.

iat (Issued At) claim represents issue time and cannot be customized. Although you can decide to include this claim in your tokens or not by setting akka.http.session.jwt.include-iat to true or false. By default, this claim is not included.

jti (JWT ID) claim is a case-sensitive string containing a unique identifier for the JWT. It must be unique per token and collisions must be prevented even among values produced by different issuers. Akka-http-session will compute and include jti claim if akka.http.session.jwt.include-jti is set to true (it's disabled by default). Token ids are generated using the below scheme:

<iss claim value>-<random UUID> or just <random UUID>, depending on the iss claim presence.

You can find a sample claims configuration below:

akka.http.session {
  jwt {
    iss = "Issuer"
    sub = "Subject"
    aud = "Audience"
    exp-timeout = 7 days
    nbf-offset = 5 minutes
    include-iat = true
    include-jti = true
  }
}

Signing session using a configurable algorithm

In the case of JWT, it's possible to configure which JWS algorithm should be used. Currently, supported ones are:

  • HS256 - HMAC using SHA-256 (used by default)
  • RS256 - RSA (RSASSA-PKCS1-v1_5) using SHA-256

All non-JWT sessions use HMAC with SHA-256 and this cannot be configured.

Configuring JWS (for JSON Web Tokens only)

In order to start using RSA algorithm you have to configure akka.http.session.jws.alg and akka.http.session.jws.rsa-private-key properties:

akka.http.session {
  jws {
    alg = "RS256"
    rsa-private-key = "<your private PKCS#8 key goes here>"
  }
}

Because HS256 is used by default you may skip the jws configuration and rely on a reference configuration delivered with the library. Alternatively, if you prefer to be more explicit, you might follow this configuration template:

akka.http.session {
  server-secret = "<at least 64-digits length secret goes here>"
  jws {
    alg = "HS256"
  }
}

You might notice that even if you want to sign your sessions using RSA key and encryption is disabled, you still have to define the server-secret property.

That's because all non-JWT sessions still depend on HMAC with SHA256 algorithm which requires the server secret and the library cannot determine which session encoder(s) will be used (it's specified in the client's code).

CSRF protection (cookie transport only)

CSRF is a kind of an attack where an attacker issues a GET or POST request on behalf of a user, if the user e.g. clicks on a specially constructed link. See the OWASP page or the Play! docs for a thorough introduction.

Web apps which use cookies for session management should be protected against CSRF attacks. This implementation:

  • assumes that GET requests are non-mutating (have no side effects)
  • uses double-submit cookies to verify requests
  • requires the token to be set in a custom header or (optionally) in a form field
  • generates a new token on the first GET request that doesn't have the token cookie set

Note that if the token is passed in a form field, the website isn't protected by HTTPS or you don't control all subdomains, this scheme can be broken. Currently, setting a custom header seems to be a secure solution, and is what a number of projects do (that's why, when using custom headers to send session data, no additional protection is needed).

It is recommended to generate a new CSRF token after logging in, see this SO question. A new token can be generated using the setNewCsrfToken directive.

By default the name of the CSRF cookie and the custom header matches what AngularJS expects and sets. These can be customized in the config.

Refresh tokens (a.k.a "remember me")

If you'd like to implement persistent, "remember-me" sessions, you should use refreshable instead of oneOff sessions. This is especially useful in mobile applications, where you log in once, and the session is remembered for a long time. Make sure to adjust the akka.http.session.refresh-token.max-age config option appropriately (defaults to 1 month)!

You can dynamically decide, basing on the request properties (e.g. a query parameter), if a session should be refreshable or not. Just pass the right parameter to setSession.

When using refreshable sessions, in addition to an (implicit) SessionManager instance, you need to provide an implementation of the RefreshTokenStorage trait. This trait has methods to lookup, store and delete refresh tokens. Typically it would use some persistent storage.

The tokens are never stored directly, instead only token hashes are passed to the storage. That way even if the token database is leaked, it won't be possible to forge sessions using the hashes. Moreover, in addition to the token hash, a selector value is stored. That value is used to lookup stored hashes; tokens are compared using a special constant-time comparison method, to prevent timing attacks.

When a session expires or is not present, but the refresh token is (sent from the client using either a cookie, or a custom header), a new session will be created (using the RefreshTokenLookupResult.createSession function), and a new refresh token will be created.

Note that you can differentiate between sessions created from refresh tokens and from regular authentication by storing appropriate information in the session data. That way, you can force the user to re-authenticate if the session was created by a refresh token before crucial operations.

It is of course possible to read oneOff-session using requiredSession(refreshable, ...). If a session was created as oneOff, using refreshable has no additional effect.

Touching sessions

The semantics of touch[Required|Optional]Session() are a bit subtle. You can still use expiring client sessions when using refresh tokens. You will then have 2 stages of expiration: expiration of the client session (should be shorter), and expiry of the refresh token. That way you can have strongly-authenticated sessions which expire fast, and weaker-authenticated re-creatable sessions (as described in the paragraph above).

When touching an existing session, the refresh token will not be re-generated and extended, only the session cookie.

Links

Using from SBT

For akka-http version 10+:

libraryDependencies += "com.softwaremill.akka-http-session" %% "core" % "0.7.1"
libraryDependencies += "com.softwaremill.akka-http-session" %% "jwt"  % "0.7.1" // optional

For pekko-http:

libraryDependencies += "com.softwaremill.pekko-http-session" %% "core" % "0.7.1"
libraryDependencies += "com.softwaremill.pekko-http-session" %% "jwt"  % "0.7.1" // optional

Updating

Certain releases changed the client token encoding/serialization. In those cases, it's important to enable the appropriate token migrations, otherwise existing client sessions will be invalid (and your users will be logged out).

When updating from a version before 0.5.3, set akka.http.session.token-migration.v0-5-3.enabled = true.

When updating from a version before 0.5.2, set akka.http.session.token-migration.v0-5-2.enabled = true.

Note that when updating through multiple releases, be sure to enable all the appropriate migrations.

For versions prior to 0.5.0, no migration path is provided. However, you can implement your own encoders/serializers to support migrating from whatever version you are using.

Since token changes may be security related, migrations should be enabled for the shortest period of time after which the vast majority of client tokens have been migrated.

Commercial Support

We offer commercial support for akka-http-session and related technologies, as well as development services. Contact us to learn more about our offer!

Copyright

Copyright (C) 2016-2023 SoftwareMill https://softwaremill.com.

More Repositories

1

elasticmq

In-memory message queue with an Amazon SQS-compatible interface. Runs stand-alone or embedded.
Scala
2,502
star
2

sttp

The Scala HTTP client you always wanted!
Scala
1,454
star
3

tapir

Rapid development of self-documenting APIs
Scala
1,363
star
4

macwire

Zero-cost, compile-time, type-safe dependency injection library.
Scala
1,266
star
5

quicklens

Modify deeply nested case class fields
Scala
822
star
6

magnolia

Easy, fast, transparent generic derivation of typeclass instances
Scala
765
star
7

bootzooka

Simple project to quickly start developing a Scala-based microservice or web application, without the need to write login, user registration etc.
Scala
695
star
8

codebrag

Your daily code review tool
Scala
651
star
9

ox

Safe direct-style concurrency and resiliency for Scala on the JVM
Scala
382
star
10

it-cfp-list

List of Call For Papers for IT conferences
374
star
11

retry

because you should never give up, at least not on the first try
Scala
351
star
12

diffx

Pretty diffs for scala case classes
Scala
344
star
13

kmq

Kafka-based message queue
Scala
330
star
14

scala-clippy

Good advice for Scala compiler errors
Scala
315
star
15

supler

Rapid Form Development library. Use your favourite JS frontend & Scala backend frameworks.
Scala
286
star
16

jox

Fast and Scalable Channels in Java
Java
248
star
17

mqperf

Scala
144
star
18

scala-common

Tiny independent libraries with a single purpose, often a single class
Scala
120
star
19

slick-eventsourcing

Example for "Entry level event-sourcing" blog
Scala
118
star
20

lemon-dataset

Lemons quality control dataset
103
star
21

maven-badges

A node.js implementation of https://github.com/jirutka/maven-badges, originally created in ruby.
TypeScript
86
star
22

sbt-softwaremill

A sane set of default build settings
Scala
72
star
23

akka-vs-scalaz

Scala
63
star
24

recursion-training

Recursion schemes training examples and exercises
HTML
59
star
25

livestub

The HTTP server stub you always wanted!
Scala
53
star
26

scala-sql-compare

Scala
50
star
27

stringmask

A micro-library for macro-based case class field masking in .toString
Scala
48
star
28

scala-id-generator

Scala
48
star
29

confluent-playground

Java
44
star
30

sttp-model

Simple Scala HTTP model
Scala
43
star
31

odelay

delayed reactions
Scala
42
star
32

saft

Scala
42
star
33

sttp-openai

Scala
41
star
34

akka-simple-cluster-k8s

Scala
39
star
35

walk-that-type

A tool for evaluating TypeScript types step by step.
TypeScript
39
star
36

softwaremill-common

SoftwareMill Common library
Java
37
star
37

OtterJet

Visualization of messages from a NATS JetStream server
Java
33
star
38

scala-pre-commit-hooks

Pre-commit/Pre-push hooks for Scala
Python
32
star
39

FoXAI

The open-source library for explainable AI. Generic and easy to integrate with PyTorch.
Python
31
star
40

neme-plugin

Scala compiler plugin for turning non exhaustive match warnings into errors
Scala
29
star
41

reactive-event-sourcing-java

Java
28
star
42

zio2-structure

Scala
26
star
43

helisa

Scala API for jenetics
Scala
26
star
44

streams-tests

Scala
25
star
45

meerkat

Observability Starter Kit for JVM Applications
JavaScript
25
star
46

tapir-loom

Scala
24
star
47

sttp-apispec

OpenAPI, AsyncAPI and JSON Schema Scala models.
Scala
23
star
48

node-typescript-starter

A basic boilerplate for node + TypeScript development with debugger source maps support.
TypeScript
22
star
49

free-tagless-compare

Free monads compared to tagless final
Scala
22
star
50

akka-http-session-faq

Java
21
star
51

activator-reactive-kafka-scala

Activator template for Reactive Kafka
Scala
20
star
52

adopt-tapir

A quickstart generator for Tapir projects
Scala
17
star
53

reactive-streams-for-java-developers

Java
17
star
54

scala3-macro-debug

Scala
17
star
55

resilience4s

Scala
16
star
56

react-use-promise-matcher

React hooks allowing you to handle promises in a stateful way
TypeScript
16
star
57

simple-http-server

Simple JVM based HTTP server with no dependencies
Scala
15
star
58

correlator

Scala
15
star
59

detectnet-tests

Python scripts and other resources for tesing DetectNet on Nvidia DIGITS
Python
14
star
60

blockchain-schedule

An experimental collaborative planning app based on Ethereum ("Decentralized Doodle")
TypeScript
14
star
61

blog-scala-structure-lifecycle

Scala
12
star
62

akka-sandbox

Training ground for experiments with Akka framework.
Scala
12
star
63

broadway-pipelines-blog

Constructing effective data processing workflows using Elixir and Broadway
Elixir
12
star
64

undelay

Satisfy Scala Futures quickly
Scala
11
star
65

monix-correlation-id

Scala
10
star
66

cassandra-monitoring

Scripts for the Cassandra Monitoring blog miniseries
10
star
67

reason-companies-example

Reason example application
OCaml
10
star
68

jvmbot

Scala
9
star
69

botarium

A simple starter kit for building bots using Node + TypeScript + BotKit.
TypeScript
8
star
70

sbt-template

Scala
8
star
71

asamal

POC for a CDI-based web lightweight framework
Java
8
star
72

boot-scala-microservice

Bootstrap microservice template that uses micro-deps library https://github.com/4finance/micro-deps
Scala
8
star
73

sttp-shared

Scala
7
star
74

modem-connector

Modulator and Demodulator for HAM Radio AX.25 audio signals
Scala
7
star
75

vehicle-routing-problem-java

Java
6
star
76

idea-pastie-plugin

Plugin to post pastie.org pasties from IntelliJ Idea
Java
5
star
77

trqbox-demo

Ruby
5
star
78

sentinel-cgan

Sentinel generative conditional adversarial network implementation
Python
5
star
79

scalatimes

Pug
5
star
80

tapir-serverless

Scala
5
star
81

scala-compiler-plugin-template

Scala
5
star
82

gatling-zeromq

A Gatling stress test plugin for ZeroMQ protocol
Scala
5
star
83

slack-alphabet

Scala
4
star
84

scalar-conf-website

Scalar - Scala Conference in Central Europe
Python
4
star
85

try-them-off

Showcase service presenting possible usage of the Try monad from Vavr.
Java
4
star
86

sttp-openapi-example

Scala
4
star
87

cache-get-or-create

Java
4
star
88

bootzooka-react

Simple project to quickly start developing a web application using React and Akka HTTP, without the need to write login, user registration etc. https://softwaremill.com/open-source/
Scala
4
star
89

fabrica

Shell
3
star
90

akka-typed-workshop

Scala
3
star
91

kuberenetes-fundamentals

Training projects to explore k8s features
Scala
3
star
92

oauth_tutorial

Phoenix OAuth tutorial
Elixir
3
star
93

terraform-gke-bootstrap

HCL
3
star
94

sttp-native-cli

Scala Native with scala-cli and sttp example
Scala
3
star
95

functional-pancakes

Scala
3
star
96

play-scala-slick-example-part2

Scala
3
star
97

ansible-bigbluebutton

Shell
3
star
98

kleisli-example

Scala
3
star
99

loom-protect

Java
3
star
100

supler-example

Example project for Supler http://supler.io
JavaScript
2
star