Session Tags
Not all users need the same session settings. Your root users need stricter security than regular users. Enterprise customers might require shorter session timeouts for compliance. Some organizations want to restrict access to their office IP addresses.
Without session tags, you’d need complex conditional logic scattered throughout your codebase. With session tags, you define the rules once and PropelAuth BYO handles the rest automatically.
What are Session Tags?
Section titled “What are Session Tags?”Session tags are labels you attach to sessions when they’re created. Each tag automatically applies specific session settings, overriding your defaults. You define the rules once in your configuration, then apply them by adding tags when creating sessions.
A tag has two parts: type and value, separated by a colon:
role:root- Tag type is “role”, value is “root”org:acme-corp- Tag type is “org”, value is “acme-corp”login_type:sso- Tag type is “login_type”, value is “sso”
When you create a session, you can add any combination of tags:
// Root user from Acme Corp gets both root rules AND org-specific rulesconst session = await client.session.create({ userId: userId, tags: ["role:root", "org:acme-corp"],});# Root user from Acme Corp gets both root rules AND org-specific rulessession = await client.session.create( user_id=user_id, tags=["role:root", "org:acme-corp"])// Root user from Acme Corp gets both root rules AND org-specific rulesCreateSessionResponse session = client.session.create( CreateSessionCommand.builder() .userId(userId) .tags(List.of("role:root", "org:acme-corp")) .build());// Root user from Acme Corp gets both root rules AND org-specific rulesvar session = await client.Session.CreateAsync(new CreateSessionCommand{ UserId = userId, Tags = new List<string> { "role:root", "org:acme-corp" }});Getting Started
Section titled “Getting Started”Let’s start with a common scenario: different session rules for root accounts vs regular users.
Step 1: Configure Your Tags
Section titled “Step 1: Configure Your Tags”In your session_config.jsonc file, define the rules for each tag:
{ "defaults": { "absolute_lifetime_secs": 1209600, // 14 days "max_concurrent_sessions_per_user": 10 }, "tags": [{ "tag": "role:root", "absolute_lifetime_secs": 14400, // 4 hours "inactivity_timeout_secs": 900, // 15 minutes "disallow_ip_address_changes": true }]}Step 2: Apply Tags When Creating Sessions
Section titled “Step 2: Apply Tags When Creating Sessions”When a user logs in, determine which tags apply and include them when creating the session:
app.post("/api/login", async (req, res) => { const user = await authenticateUser(req.body);
// Determine if this is a root account const tags = user.isRootAccount ? ["role:root"] : [];
// Create session with appropriate tags const session = await auth.session.create({ userId: user.id, ipAddress: req.ip, userAgent: req.headers["user-agent"], tags: tags, });
if (!session.ok) { return res.status(500).json({ error: "Failed to create session" }); }
res.cookie("session", session.data.sessionToken, { httpOnly: true, secure: true, sameSite: "lax", }); res.json({ success: true });});@app.post("/api/login")async def login(request: LoginRequest, response: Response): user = await authenticate_user(request)
# Determine if this is a root account tags = ["role:root"] if user.is_root_account else []
# Create session with appropriate tags session = await client.session.create( user_id=user.id, ip_address=request.client.host, user_agent=request.headers.get("user-agent"), tags=tags )
if is_err(session): raise HTTPException(status_code=500, detail="Failed to create session")
response.set_cookie( key="session", value=session.data.session_token, httponly=True, secure=True, samesite="lax" ) return {"success": True}@PostMapping("/api/login")public ResponseEntity<?> login(@RequestBody LoginRequest request, HttpServletRequest httpRequest, HttpServletResponse response) { User user = authenticateUser(request);
// Determine if this is a root account List<String> tags = user.isRootAccount() ? List.of("role:root") : List.of();
// Create session with appropriate tags CreateSessionResponse session; try { session = client.session.create( CreateSessionCommand.builder() .userId(user.getId()) .ipAddress(httpRequest.getRemoteAddr()) .userAgent(httpRequest.getHeader("User-Agent")) .tags(tags) .build() ); } catch (CreateSessionException e) { return ResponseEntity.status(500).body(Map.of("error", "Failed to create session")); }
Cookie cookie = new Cookie("session", session.getSessionToken()); cookie.setHttpOnly(true); cookie.setSecure(true); cookie.setSameSite("Lax"); response.addCookie(cookie);
return ResponseEntity.ok(Map.of("success", true));}[HttpPost("/api/login")]public async Task<IActionResult> Login([FromBody] LoginRequest request){ var user = await AuthenticateUser(request);
// Determine if this is a root account var tags = user.IsRootAccount ? new List<string> { "role:root" } : new List<string>();
// Create session with appropriate tags CreateSessionResponse session; try { session = await client.Session.CreateAsync(new CreateSessionCommand { UserId = user.Id, IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString(), UserAgent = Request.Headers["User-Agent"].ToString(), Tags = tags }); } catch (CreateSessionException) { return StatusCode(500, new { Error = "Failed to create session" }); }
Response.Cookies.Append("session", session.SessionToken, new CookieOptions { HttpOnly = true, Secure = true, SameSite = SameSiteMode.Lax });
return Ok(new { Success = true });}That’s it! Root account sessions will now automatically:
- Expire after 4 hours (instead of 14 days)
- Time out after 15 minutes of inactivity
- Get invalidated if the IP address changes
Regular users get the default settings - no extra code needed.
Common Patterns
Section titled “Common Patterns”Organization-Specific Requirements
Section titled “Organization-Specific Requirements”Different organizations often have different security requirements. Session tags make this easy:
// Tag sessions based on the user's organizationconst session = await client.session.create({ userId: user.id, tags: [`org:${user.organizationId}`], ipAddress: req.ip, userAgent: req.headers["user-agent"]});# Tag sessions based on the user's organizationsession = await client.session.create( user_id=user.id, tags=[f"org:{user.organization_id}"], ip_address=request.client.host, user_agent=request.headers.get("user-agent"))// Tag sessions based on the user's organizationCreateSessionResponse session = client.session.create( CreateSessionCommand.builder() .userId(user.getId()) .tags(List.of("org:" + user.getOrganizationId())) .ipAddress(request.getRemoteAddr()) .userAgent(request.getHeader("User-Agent")) .build());// Tag sessions based on the user's organizationvar session = await client.Session.CreateAsync(new CreateSessionCommand{ UserId = user.Id, Tags = new List<string> { $"org:{user.OrganizationId}" }, IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString(), UserAgent = Request.Headers["User-Agent"].ToString()});Your configuration might include specific rules for each organization:
{ "tags": [ { "tag": "org:acme-corp", "absolute_lifetime_secs": 28800, // 8 hours "ip_allowlist": ["203.0.113.0/24"], // Their office network "max_concurrent_sessions_per_user": 1 }, { "tag": "org:globex-inc", "absolute_lifetime_secs": 86400, // 24 hours "max_concurrent_sessions_per_user": 3 } ]}Login Type Based Sessions
Section titled “Login Type Based Sessions”Different authentication methods might need different session behaviors:
{ "tags": [ { "tag": "login_type:password", "absolute_lifetime_secs": 86400, // 1 day "inactivity_timeout_secs": 3600 // 1 hour }, { "tag": "login_type:sso", "absolute_lifetime_secs": 43200, // 12 hours "inactivity_timeout_secs": 1800 // 30 minutes }, { "tag": "login_type:passkey", "absolute_lifetime_secs": 2592000 // 30 days - more secure method } ]}Temporary or Special Access
Section titled “Temporary or Special Access”Tags are perfect for temporary access patterns:
// Grant temporary elevated accessconst session = await client.session.create({ userId: userId, tags: ["access:elevated"],});
// Trial user accessconst session = await client.session.create({ userId: guestId, tags: ["access:trial"],});# Grant temporary elevated accesssession = await client.session.create( user_id=user_id, tags=["access:elevated"])
# Trial user accesssession = await client.session.create( user_id=guest_id, tags=["access:trial"])// Grant temporary elevated accessCreateSessionResponse session = client.session.create( CreateSessionCommand.builder() .userId(userId) .tags(List.of("access:elevated")) .build());
// Trial user accessCreateSessionResponse session = client.session.create( CreateSessionCommand.builder() .userId(guestId) .tags(List.of("access:trial")) .build());// Grant temporary elevated accessvar session = await client.Session.CreateAsync(new CreateSessionCommand{ UserId = userId, Tags = new List<string> { "access:elevated" }});
// Trial user accessvar session = await client.Session.CreateAsync(new CreateSessionCommand{ UserId = guestId, Tags = new List<string> { "access:trial" }});Combining Multiple Tags
Section titled “Combining Multiple Tags”Sessions can have multiple tags, and their settings merge together. This lets you compose complex behaviors from simple rules:
// A root user from Acme Corp gets both root rules AND org-specific rulesconst session = await auth.session.create({ userId: userId, tags: ["role:root", "org:acme-corp"]});
// This session will have:// - 4 hour expiration (from role:root)// - IP restrictions if Acme Corp configured them// - Both tags' settings merged together# A root user from Acme Corp gets both root rules AND org-specific rulessession = await client.session.create( user_id=user_id, tags=["role:root", "org:acme-corp"])
# This session will have:# - 4 hour expiration (from role:root)# - IP restrictions if Acme Corp configured them# - Both tags' settings merged together// A root user from Acme Corp gets both root rules AND org-specific rulesCreateSessionResponse session = client.session.create( CreateSessionCommand.builder() .userId(userId) .tags(List.of("role:root", "org:acme-corp")) .build());
// This session will have:// - 4 hour expiration (from role:root)// - IP restrictions if Acme Corp configured them// - Both tags' settings merged together// A root user from Acme Corp gets both root rules AND org-specific rulesvar session = await client.Session.CreateAsync(new CreateSessionCommand{ UserId = userId, Tags = new List<string> { "role:root", "org:acme-corp" }});
// This session will have:// - 4 hour expiration (from role:root)// - IP restrictions if Acme Corp configured them// - Both tags' settings merged togetherWhen tags have conflicting settings, PropelAuth BYO uses a priority system to resolve them.
Tag Priority
Section titled “Tag Priority”When multiple tags define the same setting, the tag with higher priority wins. You control the priority order in your configuration:
{ "defaults": { "absolute_lifetime_secs": 1209600 // 14 days }, "tags": [{ "tag": "org:acme-corp", "absolute_lifetime_secs": 28800, // 8 hours "ip_allowlist": ["203.0.113.0/24"] // Their office network }, { "tag": "org:globex-inc", "absolute_lifetime_secs": 86400, // 24 hours "max_concurrent_sessions_per_user": 3 }, { "tag": "role:root", "absolute_lifetime_secs": 14400, // 4 hours "inactivity_timeout_secs": 900, // 15 minutes "disallow_ip_address_changes": true }, { "tag": "login_type:sso", "absolute_lifetime_secs": 43200, // 12 hours "inactivity_timeout_secs": 3600 // 1 hour }, { "tag": "login_type:passkey", "absolute_lifetime_secs": 2592000 // 30 days }],
// Organization settings override role settings "tag_priority": ["org", "role", "login_type"]}In this example:
- Organization tags (like
org:acme) have highest priority - Role tags (like
role:root) come next - Login type tags (like
login_type:sso) have lowest priority - Default settings apply if no tags match
This means if Acme Corp sets a 4-hour session limit, it overrides even root account sessions.
Validating Sessions with Tags
Section titled “Validating Sessions with Tags”When you validate a session, you can optionally verify it has specific tags:
// Validate that a session exists and has specific tagsconst validation = await client.session.validate({ sessionToken: req.cookies.session, requiredTags: ["role:root"], ipAddress: req.ip, userAgent: req.headers["user-agent"]});
if (!validation.ok) { return res.status(401).json({ error: "Unauthorized" });}
// validation.data contains user info and session metadata# Validate that a session exists and has specific tagsvalidation = await client.session.validate( session_token=request.cookies.get("session"), required_tags=["role:root"], ip_address=request.client.host, user_agent=request.headers.get("user-agent"))
if is_err(validation): raise HTTPException(status_code=401, detail="Unauthorized")
# validation.data contains user info and session metadata// Validate that a session exists and has specific tagstry { ValidateSessionResponse validation = client.session.validate( ValidateSessionCommand.builder() .sessionToken(sessionToken) .requiredTags(List.of("role:root")) .ipAddress(request.getRemoteAddr()) .userAgent(request.getHeader("User-Agent")) .build() );
// validation contains user info and session metadata} catch (ValidateSessionException e) { return ResponseEntity.status(401).body(Map.of("error", "Unauthorized"));}// Validate that a session exists and has specific tagstry{ var validation = await client.Session.ValidateAsync(new ValidateSessionCommand { SessionToken = Request.Cookies["session"], RequiredTags = new List<string> { "role:root" }, IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString(), UserAgent = Request.Headers["User-Agent"].ToString() });
// validation contains user info and session metadata}catch (ValidateSessionException){ return StatusCode(401, new { Error = "Unauthorized" });}This is useful for endpoints that require specific permissions or access levels.
Advanced Features
Section titled “Advanced Features”Immutable Tags
Section titled “Immutable Tags”Some tags shouldn’t change after a session is created. For example, if a session starts as a root session, it should stay that way. To make some tags immutable, in your session_config.jsonc, use the on_create_only_tags setting:
{ "on_create_only_tags": ["role", "access"]}Now role:root or access:impersonation tags can’t be added or removed after creation.
You can also make all tags immutable:
{ "on_create_only_tags": ["*"]}Available Tag Settings
Section titled “Available Tag Settings”Each tag can override any of these session settings:
absolute_lifetime_secs- Maximum session durationinactivity_timeout_secs- Timeout after inactivitymax_concurrent_sessions_per_user- Concurrent session limitmax_concurrent_sessions_per_user_per_tag- A separate concurrent session limit that’s BOTH per user AND per tagon_session_limit_exceeded- What to do when limit is hit (“reject_new” or “drop_least_recently_active”)disallow_ip_address_changes- Invalidate on IP changeip_allowlist- Restrict to specific IP rangesip_blocklist- Block specific IP ranges