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
```