Skip to content

Commit

Permalink
Example: Add authentication middleware example (zio#175)
Browse files Browse the repository at this point in the history
* doc(http): add Authentication Middleware example

* doc: update website

* style(*): scalafmt

* chore: update build

* chore: update build

* chore: update build
  • Loading branch information
Tushar Mathur authored May 2, 2021
1 parent 4d2d2bf commit 46c879f
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ You can checkout more examples in the examples project —
- [Streaming Response](https://github.com/dream11/zio-http/blob/main/example/src/main/scala/StreamingResponse.scala)
- [Simple Client](https://github.com/dream11/zio-http/blob/main/example/src/main/scala/SimpleClient.scala)
- [File Streaming](https://github.com/dream11/zio-http/blob/main/example/src/main/scala/FileStreaming.scala)
- [Authentication](https://github.com/dream11/zio-http/blob/main/example/src/main/scala/Authentication.scala)

# Installation

Expand Down
5 changes: 4 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,10 @@ lazy val example = (project in file("./example"))
.settings(publishSetting(false))
.settings(
fork := true,
Compile / run / mainClass := Option("HelloWorldAdvanced"),
Compile / run / mainClass := Option("Authentication"),
libraryDependencies ++= Seq(
"com.github.jwt-scala" %% "jwt-core" % "7.1.4",
),
)
.dependsOn(zhttp)

Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,4 @@ A simple Http app that responds with empty content and a `200` status code is de
- [Streaming Response](https://github.com/dream11/zio-http/blob/main/example/src/main/scala/StreamingResponse.scala)
- [Simple Client](https://github.com/dream11/zio-http/blob/main/example/src/main/scala/SimpleClient.scala)
- [File Streaming](https://github.com/dream11/zio-http/blob/main/example/src/main/scala/FileStreaming.scala)
- [Authentication](https://github.com/dream11/zio-http/blob/main/example/src/main/scala/Authentication.scala)
58 changes: 58 additions & 0 deletions example/src/main/scala/Authentication.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import pdi.jwt.{Jwt, JwtAlgorithm, JwtClaim}
import zhttp.http.{Method, _}
import zhttp.service.Server
import zio._

import java.time.Clock

object Authentication extends App {
// Secret Authentication key
val SECRET_KEY = "secretKey"

implicit val clock: Clock = Clock.systemUTC

// Helper to encode the JWT token
def jwtEncode(username: String): String = {
val json = s"""{"user": "${username}"}"""
val claim = JwtClaim { json }.issuedNow.expiresIn(60)
Jwt.encode(claim, SECRET_KEY, JwtAlgorithm.HS512)
}

// Helper to decode the JWT token
def jwtDecode(token: String): Option[JwtClaim] = {
Jwt.decode(token, SECRET_KEY, Seq(JwtAlgorithm.HS512)).toOption
}

// Authentication middleware
// Takes in a Failing HttpApp and a Succeed HttpApp which are called based on Authentication success or failure
// For each request tries to read the `X-ACCESS-TOKEN` header
// Validates JWT Claim
def authenticate[R, E](fail: HttpApp[R, E], success: JwtClaim => HttpApp[R, E]): HttpApp[R, E] = Http.flatten {
Http
.fromFunction[Request] {
_.getHeader("X-ACCESS-TOKEN")
.flatMap(header => jwtDecode(header.value.toString))
.fold[HttpApp[R, E]](fail)(success)
}
}

// Http app that requires a JWT claim
def user(claim: JwtClaim): UHttpApp = Http.collect[Request] {
case Method.GET -> Root / "user" / name / "greet" => Response.text(s"Welcome to the ZIO party! ${name}")
case Method.GET -> Root / "user" / "expiration" => Response.text(s"Expires in: ${claim.expiration.getOrElse(-1L)}")
}

// App that let's the user login
// Login is successful only if the password is the reverse of the username
def login: UHttpApp = Http.collect[Request] { case Method.GET -> Root / "login" / username / password =>
if (password.reverse == username) Response.text(jwtEncode(username))
else Response.fromHttpError(HttpError.Unauthorized("Invalid username of password\n"))
}

// Composing all the HttpApps together
val app: UHttpApp = login +++ authenticate(HttpApp.forbidden("Not allowed!"), user)

// Run it like any simple app
override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] =
Server.start(8090, app).exitCode
}
5 changes: 5 additions & 0 deletions zio-http/src/main/scala/zhttp/http/HttpApp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,9 @@ object HttpApp {
* Creates an HTTP app which always responds with a 200 status code.
*/
def ok: HttpApp[Any, Nothing] = HttpApp.empty(Status.OK)

/**
* Creates an HTTP app that responds with 403 - Forbidden status code
*/
def forbidden(msg: String): UHttpApp = Http.succeed(HttpError.Forbidden(msg).toResponse)
}
1 change: 1 addition & 0 deletions zio-http/src/main/scala/zhttp/http/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import java.nio.charset.Charset

package object http extends PathModule with RequestSyntax {
type HttpApp[-R, +E] = Http[R, E, Request, Response[R, E]]
type UHttpApp = HttpApp[Any, Nothing]
type RHttpApp[-R] = HttpApp[R, Throwable]
type Endpoint = (Method, URL)
type Route = (Method, Path)
Expand Down

0 comments on commit 46c879f

Please sign in to comment.