or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

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

authentication.mddocs/

0

# Authentication Support

1

2

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

3

4

## Capabilities

5

6

### AuthedRequest Extraction

7

8

#### Authentication Context Extractor (as)

9

10

Extract authentication context from authenticated requests.

11

12

```scala { .api }

13

/**

14

* Authentication context extractor for AuthedRequest

15

* Extracts the underlying request and authentication context

16

*/

17

object as {

18

def unapply[F[_], A](ar: AuthedRequest[F, A]): Option[(Request[F], A)]

19

}

20

```

21

22

**Usage Examples:**

23

24

```scala

25

import org.http4s.AuthedRequest

26

import org.http4s.dsl.io._

27

28

// Define a user type for authentication context

29

case class User(id: Int, name: String, roles: Set[String])

30

31

// In your authenticated routes

32

val authedRoutes = AuthedRoutes.of[User, IO] {

33

// Extract request and user context

34

case GET -> Root / "profile" as user =>

35

Ok(s"Profile for user: ${user.name}")

36

37

// Access both request and user context

38

case req @ POST -> Root / "posts" as user =>

39

for {

40

post <- req.req.as[String] // Access underlying request

41

result <- createPost(post, user.id)

42

response <- Created(s"Post created by ${user.name}")

43

} yield response

44

45

// Pattern matching on user properties

46

case GET -> Root / "admin" as user if user.roles.contains("admin") =>

47

Ok("Admin panel")

48

49

case GET -> Root / "admin" as user =>

50

Forbidden(s"User ${user.name} does not have admin access")

51

}

52

```

53

54

### Authentication Integration Patterns

55

56

#### Middleware Integration

57

58

The `as` extractor works seamlessly with http4s authentication middleware:

59

60

```scala

61

import org.http4s.server.AuthMiddleware

62

import cats.data.{Kleisli, OptionT}

63

64

// Authentication function

65

def authUser(request: Request[IO]): OptionT[IO, User] = {

66

// Extract token from Authorization header

67

val tokenOpt = request.headers.get[Authorization]

68

.collect { case Authorization(Credentials.Token(token)) => token.value }

69

70

OptionT.fromOption[IO](tokenOpt)

71

.flatMap(token => OptionT(validateToken(token)))

72

}

73

74

// Create auth middleware

75

val authMiddleware: AuthMiddleware[IO, User] =

76

AuthMiddleware(Kleisli(authUser))

77

78

// Apply middleware to routes

79

val authenticatedService = authMiddleware(authedRoutes)

80

```

81

82

#### Multiple Authentication Contexts

83

84

Handle different types of authentication contexts:

85

86

```scala

87

// Different user types

88

sealed trait AuthContext

89

case class RegularUser(id: Int, name: String) extends AuthContext

90

case class ServiceAccount(apiKey: String, permissions: Set[String]) extends AuthContext

91

92

val mixedRoutes = AuthedRoutes.of[AuthContext, IO] {

93

case GET -> Root / "user-info" as RegularUser(id, name) =>

94

Ok(s"User: $name (ID: $id)")

95

96

case GET -> Root / "user-info" as ServiceAccount(apiKey, _) =>

97

Ok(s"Service account: $apiKey")

98

99

case POST -> Root / "admin" / "action" as ServiceAccount(_, permissions)

100

if permissions.contains("admin") =>

101

Ok("Admin action executed")

102

103

case POST -> Root / "admin" / "action" as _ =>

104

Forbidden("Insufficient permissions")

105

}

106

```

107

108

#### Conditional Authentication

109

110

Use the extractor in conditional logic:

111

112

```scala

113

val conditionalRoutes = AuthedRoutes.of[User, IO] {

114

case GET -> Root / "posts" as user =>

115

// Access user's own posts and public posts

116

getVisiblePosts(user.id).flatMap { posts =>

117

Ok(posts.asJson)

118

}

119

120

case GET -> Root / "posts" / IntVar(postId) as user =>

121

getPost(postId).flatMap {

122

case Some(post) if post.authorId == user.id || post.isPublic =>

123

Ok(post.asJson)

124

case Some(_) =>

125

Forbidden("Access denied to private post")

126

case None =>

127

NotFound("Post not found")

128

}

129

130

case req @ PUT -> Root / "posts" / IntVar(postId) as user =>

131

for {

132

updateData <- req.req.as[PostUpdate]

133

post <- getPost(postId)

134

response <- post match {

135

case Some(p) if p.authorId == user.id =>

136

updatePost(postId, updateData).flatMap(_ => Ok("Post updated"))

137

case Some(_) =>

138

Forbidden("Can only update your own posts").pure[IO]

139

case None =>

140

NotFound("Post not found").pure[IO]

141

}

142

} yield response

143

}

144

```

145

146

### Authentication Utilities

147

148

#### Request Enhancement

149

150

Access the underlying request for additional processing:

151

152

```scala

153

val enhancedRoutes = AuthedRoutes.of[User, IO] {

154

case req @ GET -> Root / "secure-data" as user =>

155

val underlying = req.req // Access underlying Request[IO]

156

157

// Check additional request properties

158

val clientIP = underlying.remoteAddr.getOrElse("unknown")

159

val userAgent = underlying.headers.get[`User-Agent`].map(_.value).getOrElse("unknown")

160

161

// Log authentication event

162

logAccess(user.id, clientIP, userAgent) *>

163

Ok(s"Secure data for ${user.name}")

164

165

case req @ POST -> Root / "upload" as user =>

166

// Access request body and user context

167

req.req.decode[Multipart[IO]] { multipart =>

168

processUpload(multipart, user.id).flatMap { result =>

169

Created(s"Upload processed for user ${user.name}")

170

}

171

}

172

}

173

```

174

175

#### Role-Based Access Control

176

177

Implement role-based access patterns:

178

179

```scala

180

case class User(id: Int, name: String, roles: Set[String]) {

181

def hasRole(role: String): Boolean = roles.contains(role)

182

def hasAnyRole(requiredRoles: String*): Boolean = requiredRoles.exists(roles.contains)

183

}

184

185

val rbacRoutes = AuthedRoutes.of[User, IO] {

186

// Admin-only routes

187

case GET -> Root / "admin" / "users" as user if user.hasRole("admin") =>

188

getAllUsers().flatMap(users => Ok(users.asJson))

189

190

// Manager or admin routes

191

case GET -> Root / "reports" as user if user.hasAnyRole("manager", "admin") =>

192

getReports().flatMap(reports => Ok(reports.asJson))

193

194

// User can access own data or admin can access any user's data

195

case GET -> Root / "users" / IntVar(userId) / "data" as user =>

196

if (user.id == userId || user.hasRole("admin")) {

197

getUserData(userId).flatMap(data => Ok(data.asJson))

198

} else {

199

Forbidden("Access denied")

200

}

201

202

// Default fallback for insufficient permissions

203

case _ as user =>

204

Forbidden(s"User ${user.name} does not have sufficient permissions")

205

}

206

```

207

208

### Error Handling with Authentication

209

210

Handle authentication-related errors gracefully:

211

212

```scala

213

val errorHandlingRoutes = AuthedRoutes.of[User, IO] {

214

case req @ POST -> Root / "sensitive-action" as user =>

215

performSensitiveAction(user.id).attempt.flatMap {

216

case Right(result) =>

217

Ok(result.asJson)

218

case Left(error) => error match {

219

case _: UnauthorizedException =>

220

Unauthorized("Authentication expired")

221

case _: ForbiddenException =>

222

Forbidden("Insufficient privileges")

223

case _ =>

224

InternalServerError("An error occurred")

225

}

226

}

227

}

228

229

// Combine with regular routes using middleware

230

val allRoutes = authMiddleware(errorHandlingRoutes) <+> publicRoutes

231

```