Backend Guide for Sessions

In this guide, we’ll walk you through how to create and manage sessions via using our Python Backend SDK step-by-step. We’ll cover real world examples of handling sessions after a user logs in, how to store user sessions, and how to log out users.

Sessions Sequence Diagram for Backend Integration

1Complete config steps

If you haven't done so already complete the steps in the Sessions Quickstart Start Here.

This guide leverages Stytch session_tokens as opposed to JWTs for session authentication. Stytch offers both options depending upon your preference, check out our Tokens vs JWTs guide to help you pick what is right for your app.

2Generating a Stytch Session

Stytch grants an end user a minted Session after a successful authentication flow - in other words, after a user successfully logs in. In this example, we’ll be using Email Magic Links to authenticate a user and create a session.

Let’s assume that we’ve created and invited a new Member via the Send Invite Email endpoint. When the end user clicks the magic link they’ll be redirected to your app where you’ll complete the authentication and create a session. Below is an example of what this route should look like.

In this example, upon successful authentication, we store global session fields in a Flask Session to be used across the application logic and accessed by the client.

from flask import Flask,session

# Create a session via a successful Magic Links Authentication
@app.route("/authenticate", methods=["POST"])
def authenticate():
    token = request.args.get('token')
    try:
        resp = stytch_client.magic_links.authenticate(
          magic_links_token=token,
        )
        if resp.status_code != 200:
            return resp.status_code      
    except Exception as e:
	    logger.exception("EML Authentication exception occurred.")
        return e

    # Store Session data, including session_token, in a Flask session.
    # Flask Sessions are essentially cookies with extra secure features
    # that prevents users from tampering with cookies. 
    session['stytch_session'] = resp.session_token
    session['member_id'] = resp.member_id
    session['organization_id'] = resp.organization_id
    session['organization_slug'] = resp.organization_slug

    display_success_message(email_address=resp.email_address)

    return redirect(url_for('org_home', member=resp.member))

Below is an example Session object that will be returned from an Authenticate method, resp on line 8 in the above snippet.

{
  "member_session": {
    "member_session_id": "session-test-fe6c042b-6286-479f-8a4f-b046a6c46509",
    "member_id": "member-test-32fc5024-9c09-4da3-bd2e-c9ce4da9375f",
    "started_at": "2023-01-09T07:41:52Z",
    "last_accessed_at": "2023-01-09T07:41:52Z",
    "expires_at": "2021-08-10T07:41:52Z",
    "authentication_factors": [
        {
            "delivery_method": "email",
            "email_factor": {
                "email_address": "sandbox@stytch.com",
                "email_id": "email-test-81bf03a8-86e1-4d95-bd44-bb3495224953"
            },
            "last_authenticated_at": "2023-01-09T07:41:52Z",
            "created_at": "2023-01-09T07:41:52Z",
            "updated_at": "2023-01-09T07:41:52Z",
            "sequence_order": "PRIMARY",
            "type": "magic_link"
        }
    ],
    "custom_claims": {
      "claim1": "value1",
      "claim2": "value2"
    },
    "organization_id": "organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931",
    "roles": ["stytch_member", "editor"]
  }
}

In a typical flow, once you’ve minted a Stytch Session by completing the authentication flow on your backend, you’ll need to set the resulting session_token on the client as a cookie. We accomplish that using Flask Sessions above, which are a fancier version of cookies.

Here is an explicit example of how we’d set session_token on the client as a cookie without using Flask Sessions. While we’re using Flask for our backend in this guide, how to set a cookie may differ depending upon the framework or language that you’re using; but the basic concept is the same.

from Flask import request, make_response

def set_cookie():
	# Preparing response object
	resp = make_response('Setting session_token cookie')
	resp.set_cookie(
        key='session_token',
        value=session['stytch_session'],
        domain='www.example.com',
        path='/'
    )
	return resp

def get_cookie():
	session_token = request.cookies.get('session_token')
	return session_token

3Subsequent Session authentication and validation

In the previous step, you successfully created a Session for the logged-in Member. From here on, you’ll need to authenticate and validate the Session on any subsequent requests to a protected server-side API endpoint in your application.

You would typically implement this within a middleware function that checks for a valid Member session. Below is an example of a common function that gets the current authenticated Member while completing an underlying session authentication.

# Internal helper function for getting an authenticated Member
def get_authenticated_member():
    stytch_session = session.get('stytch_session')
    try:
        resp = stytch_client.sessions.authenticate(session_token=stytch_session)
    except Exception as e:
        logger.exception("Authentication exception")
        return None
    return resp.member

# Organization home view
# If logged in, will display session member information.
# Otherwise, redirect them to sign up or log in page.
@app.route("/", methods=["GET"])
def org_home():
    member = get_authenticated_member()
    if member:
        return redirect(url_for('org_home', member=member))
    return redirect(url_for('sign_up_or_log_in'))

4Authorization checks via RBAC

This is also where you have an opportunity to enforce an authorization check on roles and permissions using our RBAC (Role-Based Access Control) solution.

For example, let's work with a scenario where an end user wants to update their shipping address.

If you implement this with default Stytch resources and roles, you can pass the Authorization object directly to an Update Member function.

# Check Member Session for authorization
@app.route("/update-shipping-address", methods=["POST"])
def update_shipping_address():
    member = get_authenticated_member()
    try:
        resp = client.organizations.members.update(
            organization_id=member.organization_id,
            member_id=member.member_id,
            name=request.args.get('name'),
            untrusted_metadata: {
                shipping_address: request.args.get('shipping-address')
            },
            method_options: UpdateRequestOptions(
                authorization: Authorization(
                    session_token: session['stytch_session'],
                ),
            )
        )
     except Exception as e:
           return handle_exception(e)
    return redirect(url_for('member_profile'))

For custom resources that aren't owned by Stytch, you'll additionally want to check that they have the correct authorization before making an Update Member call to Stytch.

To accomplish this, you can extend the get_authenticated_member() function from earlier to simultaneously complete an authorization check during the session authenticate call.

# Complete authorization check while getting Member session
def get_authenticated_member(authorization_check=None):
    stytch_session = session.get('stytch_session')
    try:
        resp = stytch_client.sessions.authenticate(
            session_token=stytch_session,
            authorization_check=authorization_check
        )
    except Exception as e:
        logger.exception("Authentication and authorization exception")
        return None
    return resp.member

@app.route("/update-file", methods=["POST"])
def update_file():
    # Custom authorization object to confirm permissions for
    authorization_check = {
        organization_id: request.args.get('organization_id'),
        resource: "internal-concept",
        action: "update"
    }
    # If this authorization check succeeds, add your internal logic below to update the file
    member = get_authenticated_member(authorization_check)

    # if (member):
    #   update file here

If you want to learn more about RBAC and see our complete guide on this feature, you can find a full set of resources in our RBAC overview.

5Updating an existing session

Let’s say you’d like to update a Member’s Session object with new information about a Member’s role via Custom Claims or extend the Session’s lifetime. You can simply pass these as additional request parameters on any Session Authenticate call as shown below.

For example, if you'd like to update a Member's permissions to make changes to a schedule based on appointments and deliveries, you could accomplish it like the following:

def upgrade_member_to_coordinator():
    try:
        resp = client.sessions.authenticate(
          session_token=session['stytch_session'],
          custom_claims={
                "role": "coordinator",
                "read-scope": ["appointments", "deliveries"],
                "write-scope": ["calendar"]
            }
        )
        if resp.status != 200:
            return handle_error(resp.status)
        session['stytch_session'] = resp.session_token
	    return
    except Exception as e:
        return handle_exception(e)

Additionally, you could implement the get_authenticated_member() function with a session extension such that a session always expires an hour after a user's last activity.

# Extend a Member's Session by an hour by default while getting an authenticated Member
def get_authenticated_member():
     stytch_session = session.get('stytch_session')
     if not stytch_session:
        return None
     try:
        # By default, we set it to 1 hour as commonly practiced
        default_session_duration_minutes = 60
        session_expires_soon(stytch_session):
            resp = stytch_client.sessions.authenticate(
                session_token=stytch_session,
                session_duration_minutes=default_session_duration_minutes
        )
     except Exception as e:
        return handle_exception(e)
     return resp.member

For a full list of accepted request parameters, check out our Session API reference.

6Revoking a Session

When a user would like to logout, or you need to end the session for any other reason, you’ll use our Revoke Session endpoint, which provides two options:

  1. Revoke a single Member Session by providing the member_session_id, session_token, or session_jwt. This is useful when a user may have multiple sessions on different devices and you only want to revoke the current session in question.
  2. Revoke all Sessions associated with an end user by specifying their member_id. This option is helpful in cases where you need to revoke all sessions for a user; say you would like a user to prove their identity by logging in again.
@app.route("/org/logout", methods=["POST"])
def logout():
    try:
        resp = stytch_client.sessions.revoke(
 		    session_token=session['stytch_session']
        )
	   if resp.status != 200:
            return handle_error(resp.status)
	   # Clear the session managed by Flask as well if successful 
	   session.pop('stytch_session', None)
	   return redirect(url_for('org_home'))
    except Exception as e:
        return handle_exception(e)