or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

authentication.mdhttp-methods-routing.mdindex.mdpath-matching.mdquery-parameters.mdstatus-codes-responses.md
tile.json

authentication.mddocs/

Authentication Support

Authentication context extraction utilities for working with authenticated requests and user context in http4s applications.

Capabilities

AuthedRequest Extraction

Authentication Context Extractor (as)

Extract authentication context from authenticated requests.

/**
 * Authentication context extractor for AuthedRequest
 * Extracts the underlying request and authentication context
 */
object as {
  def unapply[F[_], A](ar: AuthedRequest[F, A]): Option[(Request[F], A)]
}

Usage Examples:

import org.http4s.AuthedRequest
import org.http4s.dsl.io._

// Define a user type for authentication context
case class User(id: Int, name: String, roles: Set[String])

// In your authenticated routes
val authedRoutes = AuthedRoutes.of[User, IO] {
  // Extract request and user context
  case GET -> Root / "profile" as user =>
    Ok(s"Profile for user: ${user.name}")
    
  // Access both request and user context
  case req @ POST -> Root / "posts" as user =>
    for {
      post <- req.req.as[String]  // Access underlying request
      result <- createPost(post, user.id)
      response <- Created(s"Post created by ${user.name}")
    } yield response
    
  // Pattern matching on user properties
  case GET -> Root / "admin" as user if user.roles.contains("admin") =>
    Ok("Admin panel")
    
  case GET -> Root / "admin" as user =>
    Forbidden(s"User ${user.name} does not have admin access")
}

Authentication Integration Patterns

Middleware Integration

The as extractor works seamlessly with http4s authentication middleware:

import org.http4s.server.AuthMiddleware
import cats.data.{Kleisli, OptionT}

// Authentication function
def authUser(request: Request[IO]): OptionT[IO, User] = {
  // Extract token from Authorization header
  val tokenOpt = request.headers.get[Authorization]
    .collect { case Authorization(Credentials.Token(token)) => token.value }
  
  OptionT.fromOption[IO](tokenOpt)
    .flatMap(token => OptionT(validateToken(token)))
}

// Create auth middleware
val authMiddleware: AuthMiddleware[IO, User] = 
  AuthMiddleware(Kleisli(authUser))

// Apply middleware to routes
val authenticatedService = authMiddleware(authedRoutes)

Multiple Authentication Contexts

Handle different types of authentication contexts:

// Different user types
sealed trait AuthContext
case class RegularUser(id: Int, name: String) extends AuthContext
case class ServiceAccount(apiKey: String, permissions: Set[String]) extends AuthContext

val mixedRoutes = AuthedRoutes.of[AuthContext, IO] {
  case GET -> Root / "user-info" as RegularUser(id, name) =>
    Ok(s"User: $name (ID: $id)")
    
  case GET -> Root / "user-info" as ServiceAccount(apiKey, _) =>
    Ok(s"Service account: $apiKey")
    
  case POST -> Root / "admin" / "action" as ServiceAccount(_, permissions) 
    if permissions.contains("admin") =>
    Ok("Admin action executed")
    
  case POST -> Root / "admin" / "action" as _ =>
    Forbidden("Insufficient permissions")
}

Conditional Authentication

Use the extractor in conditional logic:

val conditionalRoutes = AuthedRoutes.of[User, IO] {
  case GET -> Root / "posts" as user =>
    // Access user's own posts and public posts
    getVisiblePosts(user.id).flatMap { posts =>
      Ok(posts.asJson)
    }
    
  case GET -> Root / "posts" / IntVar(postId) as user =>
    getPost(postId).flatMap {
      case Some(post) if post.authorId == user.id || post.isPublic =>
        Ok(post.asJson)
      case Some(_) =>
        Forbidden("Access denied to private post")
      case None =>
        NotFound("Post not found")
    }
    
  case req @ PUT -> Root / "posts" / IntVar(postId) as user =>
    for {
      updateData <- req.req.as[PostUpdate]
      post <- getPost(postId)
      response <- post match {
        case Some(p) if p.authorId == user.id =>
          updatePost(postId, updateData).flatMap(_ => Ok("Post updated"))
        case Some(_) =>
          Forbidden("Can only update your own posts").pure[IO]
        case None =>
          NotFound("Post not found").pure[IO]
      }
    } yield response
}

Authentication Utilities

Request Enhancement

Access the underlying request for additional processing:

val enhancedRoutes = AuthedRoutes.of[User, IO] {
  case req @ GET -> Root / "secure-data" as user =>
    val underlying = req.req  // Access underlying Request[IO]
    
    // Check additional request properties
    val clientIP = underlying.remoteAddr.getOrElse("unknown")
    val userAgent = underlying.headers.get[`User-Agent`].map(_.value).getOrElse("unknown")
    
    // Log authentication event
    logAccess(user.id, clientIP, userAgent) *>
    Ok(s"Secure data for ${user.name}")
    
  case req @ POST -> Root / "upload" as user =>
    // Access request body and user context
    req.req.decode[Multipart[IO]] { multipart =>
      processUpload(multipart, user.id).flatMap { result =>
        Created(s"Upload processed for user ${user.name}")
      }
    }
}

Role-Based Access Control

Implement role-based access patterns:

case class User(id: Int, name: String, roles: Set[String]) {
  def hasRole(role: String): Boolean = roles.contains(role)
  def hasAnyRole(requiredRoles: String*): Boolean = requiredRoles.exists(roles.contains)
}

val rbacRoutes = AuthedRoutes.of[User, IO] {
  // Admin-only routes
  case GET -> Root / "admin" / "users" as user if user.hasRole("admin") =>
    getAllUsers().flatMap(users => Ok(users.asJson))
    
  // Manager or admin routes
  case GET -> Root / "reports" as user if user.hasAnyRole("manager", "admin") =>
    getReports().flatMap(reports => Ok(reports.asJson))
    
  // User can access own data or admin can access any user's data
  case GET -> Root / "users" / IntVar(userId) / "data" as user =>
    if (user.id == userId || user.hasRole("admin")) {
      getUserData(userId).flatMap(data => Ok(data.asJson))
    } else {
      Forbidden("Access denied")
    }
    
  // Default fallback for insufficient permissions
  case _ as user =>
    Forbidden(s"User ${user.name} does not have sufficient permissions")
}

Error Handling with Authentication

Handle authentication-related errors gracefully:

val errorHandlingRoutes = AuthedRoutes.of[User, IO] {
  case req @ POST -> Root / "sensitive-action" as user =>
    performSensitiveAction(user.id).attempt.flatMap {
      case Right(result) => 
        Ok(result.asJson)
      case Left(error) => error match {
        case _: UnauthorizedException => 
          Unauthorized("Authentication expired")
        case _: ForbiddenException => 
          Forbidden("Insufficient privileges")
        case _ => 
          InternalServerError("An error occurred")
      }
    }
}

// Combine with regular routes using middleware
val allRoutes = authMiddleware(errorHandlingRoutes) <+> publicRoutes