Skip to main content

What a session is

A session is one Identities::UserSession or one Identities::AdminSession, each a record in its own table.

  • Rails creates it when a person signs in.
  • Rails deletes it when the person signs out, or when it is older than its limit.
  • Every request that needs to know who is calling ends in one question: does the Identities::UserSession still exist?

The words this page uses​

WordMeaning
Identity systemOne of the two groups of people the API knows. Users are talent and employers. Each is an Identities::User. Admins are the Jod team on the Team Portal. Each is an Identities::Admin. The two systems never share a table, a cookie, or a line of authentication code
Identities::UserSession, Identities::AdminSessionThe two session models, one per identity system. A user's sign-in is an Identities::UserSession in identities_user_sessions. An admin's is an Identities::AdminSession in identities_admin_sessions. One record per sign-in. A person signed in on a laptop and on a phone has two
Session cookieA cookie that holds the id of the Identities::UserSession or Identities::AdminSession. Its name is jodapp_session_id for users and teamjod_session_id for admins. The browser sends it to the API on every request
Signed cookieRails writes the cookie value together with a signature made from secret_key_base. A value changed on the way fails the signature check, so a browser cannot invent a session id. The value is readable, not secret. The signature is the protection
Bearer tokenA random string that a mobile app receives once at login and sends in the Authorization: Bearer <token> header. Rails keeps only the token's SHA-256 digest, in token_digest on the Identities::UserSession
CSRF tokenA second random string on the Identities::UserSession, in csrf_token. The browser holds a copy in a readable cookie and sends it back in the X-CSRF-Token header on every request that changes data

The three callers​

Rails serves three clients.

  • Each one carries a different credential.
  • Each one is allowed to do different things.
CallerWhat it isWhat it sendsWhat it may do
The JodApp Web browser (client)The person's browser, running jodapp-webThe session cookie on every request, plus X-CSRF-Token on requests that change dataEverything. It is the only caller that signs a person in or out
The JodApp Web server (Node)The Node process that renders jodapp-web pagesThe person's session cookie, forwarded from the page requestReads only. It never changes data and never signs anyone in
The mobile appPlanned. No mobile app exists todayAuthorization: Bearer <token> on every request. No cookieEverything, with the token in place of the cookie

Legend: The dashed box is JodApp Web. Its browser and its Node server are two separate callers of one app. Grey nodes are the callers. The orange node is the subject of this page, the authentication check inside Rails. Lavender nodes are the two tables in the Identities domain. Every arrow into Rails carries a credential. Every arrow out of Rails is one Identities::UserSession or Identities::AdminSession lookup.

What Rails sends back at login, and what the browser sends next​

Picture two HTTP messages.

  • The first is the response to a successful login.
    • It carries two Set-Cookie headers and a JSON body.
  • The second is any later request from the browser that changes data.
    • It carries both cookies, added by the browser, and one header, added by the API client in the page.

Legend: Each dashed box is one HTTP message, or the browser's cookie store for .jodapp.com. Orange boxes are written by Rails. Grey boxes are held or written by the browser. The cookie attributes are in the raw HTTP below. For an admin the names are teamjod_session_id and TEAM_CSRF_TOKEN, and the domain is .teamjod.app.

The same two messages as raw HTTP, shortened:

HTTP/1.1 200 OK
Set-Cookie: jodapp_session_id=<signed session id>; Domain=.jodapp.com; Path=/; Expires=<login + 30 days>; Secure; HttpOnly; SameSite=Lax
Set-Cookie: CSRF_TOKEN=<random string>; Domain=.jodapp.com; Path=/; Expires=<login + 30 days>; Secure; SameSite=Lax
Content-Type: application/json

{ "identities_user": { ... }, "talent_access": { ... }, "employer_access": { ... } }
PATCH /identities/users/current HTTP/1.1
Host: api.jodapp.com
Cookie: jodapp_session_id=<signed session id>; CSRF_TOKEN=<random string>
X-CSRF-Token: <random string>
Content-Type: application/json

Who writes and who reads each value:

ValueWritten byRead by
The jodapp_session_id cookieRails, once, at loginRails, on every request. The browser only carries it. No script can read it
The CSRF_TOKEN cookieRails, once, at loginThe API client in the page, to fill the header. Rails never reads this cookie, even though the browser sends it
The X-CSRF-Token headerThe API client in the page, on every request that changes dataRails, which compares it with csrf_token on the Identities::UserSession

Why the CSRF cookie is readable and the session cookie is not.

  • A cross-site attack makes the person's browser send a request to our API from another site.
    • The browser attaches our cookies to that request on its own, so a cookie can never prove which page made the request.
  • The proof must be something the browser does not add by itself: a header set by our own script.
    • To set the header, the script must be able to read the value.
  • Scripts on other sites cannot read our cookies, so the token stays secret from the attacker.
  • The session cookie is the opposite case.
    • It must never be readable by any script, because a stolen session id works from anywhere.
  • The cookies and CSRF page covers the rest of CSRF.

The browser does not need to know any of this.

  • It stores what Set-Cookie tells it to store and sends it back to the same domain.
  • The page's code does one thing: copy the readable cookie into the header.
    • jodapp-web does that in one place, its API client, so no page has to remember it.