Session Quick Start | PropelAuth BYO Documentation
Session Quick Start
Sessions are how your application remembers that a user is logged in. This guide will get you from zero to a working login/logout flow in just a few minutes.
By the end, you’ll have:
- A backend endpoint that creates sessions on login
- Session validation for protected routes
- A complete logout flow
Prerequisites
Before starting, make sure you have:
- PropelAuth BYO installed and running
- Backend client library set up
- A way to authenticate users (we’ll assume you have this - could be email/password, OAuth, etc.)
Step 1: Create Your First Session
When a user successfully logs in, create a session for them and store it as an HTTP-only cookie:
Node
app.post("/api/login", async (req, res) => {
// Verify the user's credentials (your existing logic)
const userId = await verifyUserCredentials(req.body);
const result = await client.session.create({
userId: userId,
});
if (!result.ok) {
console.error("Session creation failed:", result.error);
return res.status(500).json({ error: "Failed to create session" });
}
// Store the session token as an HTTP-only cookie
res.cookie("sessionToken", result.data.sessionToken, {
httpOnly: true,
secure: true,
sameSite: "lax",
});
res.json({ success: true });
});
Python
class LoginRequest(BaseModel):
# Your login fields
pass
@app.post("/api/login")
async def login(request: LoginRequest, response: Response):
# Verify the user's credentials (your existing logic)
user_id = await verify_user_credentials(request)
result = await client.session.create(user_id=user_id)
if is_err(result):
print("Session creation failed:", result.error)
raise HTTPException(status_code=500, detail="Failed to create session")
# Store the session token as an HTTP-only cookie
response.set_cookie(
key="sessionToken",
value=result.data.session_token,
httponly=True,
secure=True,
samesite="lax"
)
return {"success": True}
Go
mux.HandleFunc("POST /api/login", func(w http.ResponseWriter, r *http.Request) {
// Verify the user's credentials (your existing logic)
userID := verifyUserCredentials(r)
result, err := client.Session.Create(r.Context(), byo.CreateSessionCommand{
UserID: userID,
})
if err != nil {
log.Println("Session creation failed:", err)
http.Error(w, "Failed to create session", http.StatusInternalServerError)
return
}
// Store the session token as an HTTP-only cookie
http.SetCookie(w, &http.Cookie{
Name: "sessionToken",
Value: result.SessionToken,
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteLaxMode,
})
json.NewEncoder(w).Encode(map[string]any{"success": true})
})
Java
@PostMapping("/api/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request, HttpServletResponse response) {
// Verify the user's credentials (your existing logic)
String userId = verifyUserCredentials(request);
try {
CreateSessionResponse result = client.session.create(
CreateSessionCommand.builder()
.userId(userId)
.build()
);
// Store the session token as an HTTP-only cookie
Cookie cookie = new Cookie("sessionToken", result.getSessionToken());
cookie.setHttpOnly(true);
cookie.setSecure(true);
cookie.setPath("/");
response.addCookie(cookie);
return ResponseEntity.ok(Map.of("success", true));
} catch (CreateSessionException e) {
System.err.println("Session creation failed: " + e.getMessage());
return ResponseEntity.status(500).body(Map.of("error", "Failed to create session"));
}
}
.NET
[HttpPost("/api/login")]
public async Task<IActionResult> Login([FromBody] LoginRequest request)
{
// Verify the user's credentials (your existing logic)
var userId = await VerifyUserCredentials(request);
try
{
var result = await client.Session.CreateAsync(new CreateSessionCommand
{
UserId = userId
});
// Store the session token as an HTTP-only cookie
Response.Cookies.Append("sessionToken", result.SessionToken, new CookieOptions
{
HttpOnly = true,
Secure = true,
SameSite = SameSiteMode.Lax
});
return Ok(new { Success = true });
}
catch (CreateSessionException ex)
{
Console.Error.WriteLine($"Session creation failed: {ex.Message}");
return StatusCode(500, new { Error = "Failed to create session" });
}
}
Now when the user logs in, they’ll receive a cookie that automatically gets sent with every request.
Step 2: Validate Sessions on Protected Routes
For any route that requires authentication, validate the session token:
Node
app.get("/api/protected-route", async (req, res) => {
const sessionToken = req.cookies.sessionToken;
const validation = await client.session.validate({
sessionToken,
});
if (!validation.ok) {
console.error("Session validation failed:", validation.error);
return res.status(401).json({ error: "Unauthorized" });
}
// The session is valid! You can access the user ID and metadata
const { userId, metadata } = validation.data;
res.json({
message: "You're authenticated!",
userId,
metadata,
});
});
Python
@app.get("/api/protected-route")
async def protected_route(request: Request):
session_token = request.cookies.get("sessionToken")
validation = await client.session.validate(session_token=session_token)
if is_err(validation):
print("Session validation failed:", validation.error)
raise HTTPException(status_code=401, detail="Unauthorized")
user_id = validation.data.user_id
metadata = validation.data.metadata
return {
"message": "You're authenticated!",
"userId": user_id,
"metadata": metadata,
}
Go
mux.HandleFunc("GET /api/protected-route", func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("sessionToken")
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
validation, err := client.Session.Validate(r.Context(), byo.ValidateSessionCommand{
SessionToken: byo.String(cookie.Value),
})
if err != nil {
log.Println("Session validation failed:", err)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
json.NewEncoder(w).Encode(map[string]any{
"message": "You're authenticated!",
"userId": validation.UserID,
"metadata": validation.Metadata,
})
})
Java
@GetMapping("/api/protected-route")
public ResponseEntity<?> protectedRoute(@CookieValue(name = "sessionToken", required = false) String sessionToken) {
try {
ValidateSessionResponse validation = client.session.validate(
ValidateSessionCommand.builder()
.sessionToken(sessionToken)
.build()
);
String userId = validation.getUserId();
JsonValue metadata = validation.getMetadata();
return ResponseEntity.ok(Map.of(
"message", "You're authenticated!",
"userId", userId,
"metadata", metadata
));
} catch (ValidateSessionException e) {
System.err.println("Session validation failed: " + e.getMessage());
return ResponseEntity.status(401).body(Map.of("error", "Unauthorized"));
}
}
.NET
[HttpGet("/api/protected-route")]
public async Task<IActionResult> ProtectedRoute()
{
var sessionToken = Request.Cookies["sessionToken"];
try
{
var validation = await client.Session.ValidateAsync(new ValidateSessionCommand
{
SessionToken = sessionToken
});
var userId = validation.UserId;
var metadata = validation.Metadata;
return Ok(new
{
Message = "You're authenticated!",
UserId = userId,
Metadata = metadata
});
}
catch (ValidateSessionException ex)
{
Console.Error.WriteLine($"Session validation failed: {ex.Message}");
return StatusCode(401, new { Error = "Unauthorized" });
}
}
The Validate Session function checks if:
- The token exists and is valid
- The session hasn’t expired
- The session hasn’t been invalidated
If validation succeeds, you get back the user ID and any metadata you stored when creating the session. If validation fails, you get a detailed error explaining exactly why (e.g. IP address not allowed, session expired, etc.).
Step 3: Add a Frontend
Now let’s connect a frontend. Here’s a simple login form that sends credentials to your backend:
function LoginForm() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleLogin = async (e) => {
e.preventDefault();
const response = await fetch("/api/login", {
method: "POST",
credentials: "include", // Important: include cookies
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
});
if (response.ok) {
// Redirect to dashboard or protected area
window.location.href = "/dashboard";
} else {
// Handle error
const error = await response.json();
alert(error.error);
}
};
return (
<form onSubmit={handleLogin}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
required
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
required
/>
<button type="submit">Log In</button>
</form>
);
}
Step 4: Handle Logout
When users log out, invalidate their session to ensure it can’t be used again:
Node
app.post("/api/logout", async (req, res) => {
const sessionToken = req.cookies.sessionToken;
await client.session.invalidateByToken({
sessionToken,
});
// Clear the cookie
res.clearCookie("sessionToken");
res.json({ success: true });
});
Python
@app.post("/api/logout")
async def logout(request: Request, response: Response):
session_token = request.cookies.get("sessionToken")
await client.session.invalidate_by_token(session_token=session_token)
response.delete_cookie("sessionToken")
return {"success": True}
Go
mux.HandleFunc("POST /api/logout", func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("sessionToken")
if err == nil {
_, _ = client.Session.InvalidateByToken(r.Context(), byo.InvalidateSessionByTokenCommand{
SessionToken: byo.String(cookie.Value),
})
}
http.SetCookie(w, &http.Cookie{Name: "sessionToken", Value: "", MaxAge: -1})
json.NewEncoder(w).Encode(map[string]any{"success": true})
})
Java
@PostMapping("/api/logout")
public ResponseEntity<?> logout(@CookieValue(name = "sessionToken", required = false) String sessionToken, HttpServletResponse response) {
client.session.invalidateByToken(
InvalidateSessionByTokenCommand.builder()
.sessionToken(sessionToken)
.build()
);
Cookie cookie = new Cookie("sessionToken", "");
cookie.setMaxAge(0);
response.addCookie(cookie);
return ResponseEntity.ok(Map.of("success", true));
}
.NET
[HttpPost("/api/logout")]
public async Task<IActionResult> Logout()
{
var sessionToken = Request.Cookies["sessionToken"];
await client.Session.InvalidateByTokenAsync(new InvalidateSessionByTokenCommand
{
SessionToken = sessionToken
});
Response.Cookies.Delete("sessionToken");
return Ok(new { Success = true });
}
And the corresponding frontend:
function LogoutButton() {
const handleLogout = async () => {
const response = await fetch("/api/logout", {
method: "POST",
credentials: "include", // Include the session cookie
});
if (response.ok) {
// Redirect to login page
window.location.href = "/login";
}
};
return <button onClick={handleLogout}>Log Out</button>;
}
You’re Done!
You now have a working session management system. Users can log in, stay logged in across requests, and securely log out.
Adding Security Features
Your basic session management is working, but you can enhance security with just a few additions:
Detect User Agent Changes
When creating the session and when validating, include the user agent:
Node
const session = await auth.session.create({
userId: userId,
userAgent: req.headers["user-agent"],
});
Python
session = await client.session.create(
user_id=user_id,
user_agent=request.headers.get("user-agent")
)
Go
session, err := client.Session.Create(ctx, byo.CreateSessionCommand{
UserID: userID,
UserAgent: byo.String(r.UserAgent()),
})
Java
CreateSessionResponse session = client.session.create(
CreateSessionCommand.builder()
.userId(userId)
.userAgent(request.getHeader("User-Agent"))
.build()
);
.NET
var session = await client.Session.CreateAsync(new CreateSessionCommand
{
UserId = userId,
UserAgent = Request.Headers["User-Agent"].ToString()
});
Add IP Address Checks / Logging
When creating the session and when validating, include the IP address:
Node
const session = await client.session.create({
userId: userId,
ipAddress: req.ip,
});
Python
session = await client.session.create(
user_id=user_id,
ip_address=request.client.host
)
Go
session, err := client.Session.Create(ctx, byo.CreateSessionCommand{
UserID: userID,
IPAddress: byo.String(r.RemoteAddr),
})
Java
CreateSessionResponse session = client.session.create(
CreateSessionCommand.builder()
.userId(userId)
.ipAddress(request.getRemoteAddr())
.build()
);
Store User Data in Sessions
When creating the session, you can also store user metadata:
Node
const session = await client.session.create({
userId: userId,
metadata: {
email: user.email,
role: user.role,
teamId: user.teamId,
},
});
Python
session = await client.session.create(
user_id=user_id,
metadata={
"email": user.email,
"role": user.role,
"teamId": user.team_id,
}
)
What’s Next?
Now that you have sessions working, you can:
- Configure session behavior
- Add device registration
- Use session tags
- Add JWTs
Check out the Session Overview for a complete guide to all features, or dive into the API Reference for detailed documentation.