skills/managing-user-sessions/SKILL.md
Manages Scalekit-backed user sessions by securely storing access/refresh/ID tokens (with encryption and correct cookie attributes), validating access tokens on every request, transparently refreshing tokens in middleware, and optionally revoking sessions remotely via Scalekit session APIs. Use when building session persistence for only for web apps. For SPAs this is NOT the skill.
npx skillsauth add scalekit-inc/skills managing-user-sessionsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This SKILL.md must include name and description frontmatter fields, and the description should be written in third person for reliable skill discovery.
After successful authentication, the app receives session tokens (typically access + refresh, and sometimes an ID token) that determine how long the user stays signed in and whether refresh can happen without re-authentication.
This skill implements a secure default for traditional web apps (encrypted HttpOnly cookies) and also supports SPA/mobile patterns (access token in memory + Authorization: Bearer headers).
accessToken, refreshToken, idToken).Secure in production (HTTPS-only) and set SameSite to Strict (or Lax if Strict breaks auth redirects).Path to reduce exposure:
/api (or your protected routes) when possible./auth/refresh).Use encryption-in-cookie as an extra layer, then store:
import cookieParser from "cookie-parser";
app.use(cookieParser());
// Example after successful authentication:
const { accessToken, expiresIn, refreshToken, idToken } = authResult;
// Encrypt before storing (implementation is app-specific)
const encAccess = encrypt(accessToken);
const encRefresh = encrypt(refreshToken);
// Access token: short-lived, cookie scoped
res.cookie("accessToken", encAccess, {
maxAge: (expiresIn - 60) * 1000, // clock-skew buffer
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
path: "/api",
});
// Refresh token: separate cookie, scoped to refresh endpoint
res.cookie("refreshToken", encRefresh, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
path: "/auth/refresh",
});
// Optional: ID token for logout (only if your logout needs it)
if (idToken) {
res.cookie("idToken", idToken, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
path: "/",
});
}
from flask import make_response
import os
# auth_result: access_token, expires_in, refresh_token, id_token (optional)
enc_access = encrypt(auth_result.access_token)
enc_refresh = encrypt(auth_result.refresh_token)
resp = make_response()
resp.set_cookie(
"accessToken",
enc_access,
max_age=auth_result.expires_in - 60,
httponly=True,
secure=os.environ.get("FLASK_ENV") == "production",
samesite="Strict",
path="/api",
)
resp.set_cookie(
"refreshToken",
enc_refresh,
httponly=True,
secure=os.environ.get("FLASK_ENV") == "production",
samesite="Strict",
path="/auth/refresh",
)
if getattr(auth_result, "id_token", None):
resp.set_cookie(
"idToken",
auth_result.id_token,
httponly=True,
secure=os.environ.get("FLASK_ENV") == "production",
samesite="Strict",
path="/",
)
// accessToken, refreshToken, expiresIn come from your auth completion result
encAccess := encrypt(accessToken)
encRefresh := encrypt(refreshToken)
c.SetSameSite(http.SameSiteStrictMode)
c.SetCookie("accessToken", encAccess, expiresIn-60, "/api", "", isProd(), true)
c.SetCookie("refreshToken", encRefresh, 0, "/auth/refresh", "", isProd(), true)
// Optional
if idToken != "" {
c.SetCookie("idToken", idToken, 0, "/", "", isProd(), true)
}
// Encrypt tokens before storing (implementation is app-specific)
String encAccess = encrypt(authResult.getAccessToken());
String encRefresh = encrypt(authResult.getRefreshToken());
Cookie access = new Cookie("accessToken", encAccess);
access.setMaxAge(authResult.getExpiresIn() - 60);
access.setHttpOnly(true);
access.setSecure(isProd());
access.setPath("/api");
response.addCookie(access);
// Ensure SameSite is applied (implementation depends on your framework version)
Cookie refresh = new Cookie("refreshToken", encRefresh);
refresh.setHttpOnly(true);
refresh.setSecure(isProd());
refresh.setPath("/auth/refresh");
response.addCookie(refresh);
For SPAs and mobile apps, prefer:
Authorization: Bearer <token>.export async function verifySession(req, res, next) {
const accessCookie = req.cookies?.accessToken;
const refreshCookie = req.cookies?.refreshToken;
if (!accessCookie) return res.status(401).json({ error: "Authentication required" });
try {
const accessToken = decrypt(accessCookie);
const isValid = await scalekit.validateAccessToken(accessToken);
if (isValid) return next();
// Not valid -> attempt refresh
if (!refreshCookie) {
return res.status(401).json({ error: "Session expired. Please sign in again." });
}
const refreshToken = decrypt(refreshCookie);
const authResult = await scalekit.refreshAccessToken(refreshToken);
res.cookie("accessToken", encrypt(authResult.accessToken), {
maxAge: (authResult.expiresIn - 60) * 1000,
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
path: "/api",
});
res.cookie("refreshToken", encrypt(authResult.refreshToken), {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
path: "/auth/refresh",
});
return next();
} catch (e) {
return res.status(401).json({ error: "Authentication failed" });
}
}
from functools import wraps
from flask import request, jsonify, make_response
def verify_session(f):
@wraps(f)
def inner(*args, **kwargs):
access_cookie = request.cookies.get("accessToken")
refresh_cookie = request.cookies.get("refreshToken")
if not access_cookie:
return jsonify({"error": "Authentication required"}), 401
try:
access_token = decrypt(access_cookie)
is_valid = scalekit_client.validate_access_token(access_token)
if is_valid:
return f(*args, **kwargs)
if not refresh_cookie:
return jsonify({"error": "Session expired. Please sign in again."}), 401
refresh_token = decrypt(refresh_cookie)
auth_result = scalekit_client.refresh_access_token(refresh_token)
resp = make_response(f(*args, **kwargs))
resp.set_cookie("accessToken", encrypt(auth_result.access_token),
max_age=auth_result.expires_in - 60, httponly=True,
secure=is_prod(), samesite="Strict", path="/api")
resp.set_cookie("refreshToken", encrypt(auth_result.refresh_token),
httponly=True, secure=is_prod(), samesite="Strict", path="/auth/refresh")
return resp
except Exception:
return jsonify({"error": "Authentication failed"}), 401
return inner
func VerifySession() gin.HandlerFunc {
return func(c *gin.Context) {
accessCookie, err := c.Cookie("accessToken")
if err != nil || accessCookie == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error":"Authentication required"})
c.Abort()
return
}
accessToken := decrypt(accessCookie)
isValid, err := scalekitClient.ValidateAccessToken(accessToken)
if err == nil && isValid {
c.Next()
return
}
refreshCookie, err := c.Cookie("refreshToken")
if err != nil || refreshCookie == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error":"Session expired. Please sign in again."})
c.Abort()
return
}
refreshToken := decrypt(refreshCookie)
authResult, err := scalekitClient.RefreshAccessToken(refreshToken)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error":"Session expired. Please sign in again."})
c.Abort()
return
}
c.SetSameSite(http.SameSiteStrictMode)
c.SetCookie("accessToken", encrypt(authResult.AccessToken), authResult.ExpiresIn-60, "/api", "", isProd(), true)
c.SetCookie("refreshToken", encrypt(authResult.RefreshToken), 0, "/auth/refresh", "", isProd(), true)
c.Next()
}
}
Implement HandlerInterceptor#preHandle (or a filter) to:
Session behavior should be adjustable without code changes (typical policy knobs):
Use Scalekit session APIs to implement:
const sessionDetails = await scalekit.session.getSession("ses_1234567890123456");
const userSessions = await scalekit.session.getUserSessions("usr_1234567890123456", {
pageSize: 10,
filter: { status: ["ACTIVE"] }
});
await scalekit.session.revokeSession("ses_1234567890123456");
await scalekit.session.revokeAllUserSessions("usr_1234567890123456");
HttpOnly, Secure (in prod), and SameSite is set intentionally.Path scoping works: refresh token cookie is only sent to /auth/refresh.Path=/auth/refresh).tools
Create or review Scalekit custom providers/connectors for proxy-only usage, including MCP providers. Use this skill when the task is to gather API docs, infer whether a connector is OAuth, Basic, Bearer, or API Key, determine if it is an MCP provider, determine required tracked fields like domain or version, generate provider JSON, check for existing custom providers, show update diffs, run approved create or update curls, and print resolved delete curls.
tools
Use when a developer is new to Scalekit and needs guidance on where to start, doesn't know which auth plugin or skill to choose, wants to connect an AI agent or agentic workflow to third-party services (Gmail, Slack, Notion, Google Calendar), needs OAuth or tool-calling auth for agents, wants to add authentication to a project but hasn't chosen an approach yet, or needs to install the Scalekit plugin for their AI coding tool (Claude Code, Codex, Copilot CLI, Cursor, or other agents).
tools
Use when a user asks to generate, review, validate, or fix any code snippet that uses Scalekit APIs or SDKs. This skill is the single source of truth for Scalekit code correctness — it can generate illustration-quality snippets from scratch (for docs, websites, or integration guides) and review existing code to catch wrong method names, missing parameters, security anti-patterns, and broken auth flows. Covers all four SDKs (Node, Python, Go, Java), raw REST API calls, and both Scalekit product suites — SaaSKit (SSO, login, sessions, RBAC, SCIM) and AgentKit (connections, tool calling, MCP auth). Use when the user says review my Scalekit code, generate a Scalekit example, validate this auth flow, check my SDK usage, fix my Scalekit integration, write a code sample for docs, or anything involving Scalekit code quality.
development
Walks through a structured production readiness checklist for Scalekit SSO implementations. Use when the user says they are going live, launching to production, doing a pre-launch review, hardening their SSO setup, or wants to verify their Scalekit implementation is production-ready.