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:

Prerequisites

Before starting, make sure you have:

  1. PropelAuth BYO installed and running
  2. Backend client library set up
  3. 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:

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:

Check out the Session Overview for a complete guide to all features, or dive into the API Reference for detailed documentation.