plugins/full-stack-auth/skills/manage-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/claude-code-authstack 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).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.
data-ai
Implements complete SSO and authentication flows using Scalekit. Handles modular SSO, IdP-initiated login, user session management, and enterprise customer onboarding. Use when adding authentication, SSO, SAML, OIDC, or user login to applications.
testing
Implements Scalekit's admin portal for customer self-serve SSO and SCIM configuration. Generates portal links server-side and embeds the portal as an iframe in the app's settings UI. Use when the user asks to add an admin portal, customer self-serve SSO setup, iframe embed for SSO config, shareable setup link, or let customers configure their own SSO or SCIM connection.
development
Walks through a structured production readiness checklist for Scalekit SCIM provisioning implementations. Use when the user says they are going live, launching to production, doing a pre-launch review, or wants to verify their SCIM directory sync implementation is production-ready.