diff --git a/Cargo.lock b/Cargo.lock
index 5cbc61fb..9d87eac8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -59,6 +59,26 @@ dependencies = [
  "trust-dns-resolver",
 ]
 
+[[package]]
+name = "actix-files"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d031468a7859f71674e5531bd05137e0ea5de05ec9a917314330b88c582e2e0a"
+dependencies = [
+ "actix-service",
+ "actix-web",
+ "bitflags",
+ "bytes 0.5.6",
+ "derive_more",
+ "futures-core",
+ "futures-util",
+ "log",
+ "mime",
+ "mime_guess",
+ "percent-encoding",
+ "v_htmlescape",
+]
+
 [[package]]
 name = "actix-http"
 version = "2.2.0"
@@ -394,7 +414,7 @@ checksum = "796540673305a66d127804eef19ad696f1f204b8c1025aaca4958c17eab32877"
 dependencies = [
  "getrandom 0.2.2",
  "once_cell",
- "version_check",
+ "version_check 0.9.3",
 ]
 
 [[package]]
@@ -596,6 +616,15 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "buf-min"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "881e704e61d0fb41d7c6c9ae2bd790eb8c13dc974ae102fb98c788b4fdea4349"
+dependencies = [
+ "bytes 0.6.0",
+]
+
 [[package]]
 name = "build_const"
 version = "0.2.1"
@@ -620,6 +649,12 @@ version = "0.5.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
 
+[[package]]
+name = "bytes"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0dcbc35f504eb6fc275a6d20e4ebcda18cf50d40ba6fabff8c711fa16cb3b16"
+
 [[package]]
 name = "bytes"
 version = "1.0.1"
@@ -732,7 +767,7 @@ dependencies = [
  "rand 0.8.3",
  "sha2",
  "time 0.2.26",
- "version_check",
+ "version_check 0.9.3",
 ]
 
 [[package]]
@@ -954,6 +989,18 @@ dependencies = [
  "termcolor",
 ]
 
+[[package]]
+name = "filetime"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "redox_syscall",
+ "winapi 0.3.9",
+]
+
 [[package]]
 name = "flate2"
 version = "1.0.20"
@@ -1122,7 +1169,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
 dependencies = [
  "typenum",
- "version_check",
+ "version_check 0.9.3",
 ]
 
 [[package]]
@@ -1162,6 +1209,7 @@ name = "guard"
 version = "0.1.0"
 dependencies = [
  "actix",
+ "actix-files",
  "actix-http",
  "actix-identity",
  "actix-rt 1.1.1",
@@ -1176,6 +1224,7 @@ dependencies = [
  "m_captcha",
  "pretty_env_logger",
  "rand 0.8.3",
+ "sailfish",
  "serde 1.0.125",
  "serde_json",
  "sqlx",
@@ -1265,6 +1314,15 @@ dependencies = [
  "digest",
 ]
 
+[[package]]
+name = "home"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654"
+dependencies = [
+ "winapi 0.3.9",
+]
+
 [[package]]
 name = "hostname"
 version = "0.3.1"
@@ -1385,6 +1443,12 @@ version = "0.4.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
 
+[[package]]
+name = "itoap"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9028f49264629065d057f340a86acb84867925865f73bbf8d47b4d149a7e88b8"
+
 [[package]]
 name = "js-sys"
 version = "0.3.49"
@@ -1558,6 +1622,16 @@ version = "0.3.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
 
+[[package]]
+name = "mime_guess"
+version = "2.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
 [[package]]
 name = "miniz_oxide"
 version = "0.4.4"
@@ -1649,6 +1723,16 @@ version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
 
+[[package]]
+name = "nom"
+version = "4.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
+dependencies = [
+ "memchr",
+ "version_check 0.1.5",
+]
+
 [[package]]
 name = "nom"
 version = "5.1.2"
@@ -1657,7 +1741,7 @@ checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
 dependencies = [
  "lexical-core",
  "memchr",
- "version_check",
+ "version_check 0.9.3",
 ]
 
 [[package]]
@@ -1670,7 +1754,7 @@ dependencies = [
  "funty",
  "lexical-core",
  "memchr",
- "version_check",
+ "version_check 0.9.3",
 ]
 
 [[package]]
@@ -1912,7 +1996,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "syn",
- "version_check",
+ "version_check 0.9.3",
 ]
 
 [[package]]
@@ -1923,7 +2007,7 @@ checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
 dependencies = [
  "proc-macro2",
  "quote",
- "version_check",
+ "version_check 0.9.3",
 ]
 
 [[package]]
@@ -2156,6 +2240,43 @@ version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
 
+[[package]]
+name = "sailfish"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5ef143e1c551a0eee1d1c4a1faf45558749750826db1eab212babbe08e4a7b8"
+dependencies = [
+ "itoap",
+ "ryu",
+ "sailfish-macros",
+ "version_check 0.9.3",
+]
+
+[[package]]
+name = "sailfish-compiler"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b823601d78663c108d4425fea46ee63e1622144eadbcf0eeb9c279c1be681cd9"
+dependencies = [
+ "filetime",
+ "home",
+ "memchr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "yaml-rust",
+]
+
+[[package]]
+name = "sailfish-macros"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90b31a6c9be3d1bd5fa41a6d5c274ba3c7d1ebb1d0e0b6381e60cc70725e9e10"
+dependencies = [
+ "proc-macro2",
+ "sailfish-compiler",
+]
+
 [[package]]
 name = "scopeguard"
 version = "1.1.0"
@@ -2452,7 +2573,7 @@ version = "0.2.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff"
 dependencies = [
- "version_check",
+ "version_check 0.9.3",
 ]
 
 [[package]]
@@ -2644,7 +2765,7 @@ dependencies = [
  "standback",
  "stdweb",
  "time-macros",
- "version_check",
+ "version_check 0.9.3",
  "winapi 0.3.9",
 ]
 
@@ -2868,6 +2989,15 @@ version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
 
+[[package]]
+name = "unicase"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
+dependencies = [
+ "version_check 0.9.3",
+]
+
 [[package]]
 name = "unicode-bidi"
 version = "0.3.4"
@@ -2938,6 +3068,38 @@ version = "0.7.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7"
 
+[[package]]
+name = "v_escape"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccca9e73c678b882900cbaec16dae4d3662ace5a17774ac45af04e0f3988fafa"
+dependencies = [
+ "buf-min",
+ "v_escape_derive",
+]
+
+[[package]]
+name = "v_escape_derive"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c860ad1273f4eee7006cee05db20c9e60e5d24cba024a32e1094aa8e574f3668"
+dependencies = [
+ "nom 4.2.3",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "v_htmlescape"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db00c903248abee8499af60bf20d242e7882335bbbffd2614915184cbb207402"
+dependencies = [
+ "cfg-if 1.0.0",
+ "v_escape",
+]
+
 [[package]]
 name = "validator"
 version = "0.13.0"
@@ -2977,6 +3139,12 @@ version = "0.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ad9680608df133af2c1ddd5eaf1ddce91d60d61b6bc51494ef326458365a470a"
 
+[[package]]
+name = "version_check"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
+
 [[package]]
 name = "version_check"
 version = "0.9.3"
diff --git a/Cargo.toml b/Cargo.toml
index 522e678e..a8690b59 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -27,6 +27,7 @@ actix = "0.11"
 actix-identity = "0.3"
 actix-http = "2.2"
 actix-rt = "1"
+actix-files = "0.4"
 
 futures = "0.3"
 
@@ -53,3 +54,5 @@ lazy_static = "1.4"
 m_captcha = { version = "0.1.2", git = "https://github.com/mCaptcha/mCaptcha" }
 
 rand = "0.8"
+
+sailfish = "0.3.2"
diff --git a/src/api/v1/mod.rs b/src/api/v1/mod.rs
index 99492777..16fead90 100644
--- a/src/api/v1/mod.rs
+++ b/src/api/v1/mod.rs
@@ -22,6 +22,10 @@ pub mod mcaptcha;
 pub mod meta;
 
 pub fn services(cfg: &mut ServiceConfig) {
+    // meta
+    cfg.service(meta::build_details);
+    cfg.service(meta::health);
+
     // auth
     cfg.service(auth::signout);
     cfg.service(auth::signin);
@@ -53,10 +57,6 @@ pub fn services(cfg: &mut ServiceConfig) {
 
     // pow
     cfg.service(mcaptcha::pow::get_config);
-
-    // meta
-    cfg.service(meta::build_details);
-    cfg.service(meta::health);
 }
 
 #[cfg(test)]
diff --git a/src/main.rs b/src/main.rs
index 2c1b9ca3..a1a668c2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -16,6 +16,7 @@
 */
 use std::env;
 
+use actix_files::Files;
 use actix_identity::{CookieIdentityPolicy, IdentityService};
 use actix_web::{
     client::Client, error::InternalError, http::StatusCode, middleware, web::JsonConfig, App,
@@ -30,6 +31,7 @@ mod errors;
 //mod routes;
 mod api;
 mod settings;
+//mod templates;
 #[cfg(test)]
 #[macro_use]
 mod tests;
@@ -65,14 +67,17 @@ async fn main() -> std::io::Result<()> {
     HttpServer::new(move || {
         let client = Client::default();
         App::new()
+            .configure(v1_services)
             .wrap(middleware::Logger::default())
             .wrap(get_identity_service())
             .wrap(middleware::Compress::default())
             .data(data.clone())
             .data(client.clone())
-            .wrap(middleware::NormalizePath::default())
+            .wrap(middleware::NormalizePath::new(
+                middleware::normalize::TrailingSlash::Trim,
+            ))
             .app_data(get_json_err())
-            .configure(v1_services)
+            .service(Files::new("/", "./static"))
     })
     .bind(SETTINGS.server.get_ip())
     .unwrap()
diff --git a/src/templates/mod.rs b/src/templates/mod.rs
new file mode 100644
index 00000000..18ffd480
--- /dev/null
+++ b/src/templates/mod.rs
@@ -0,0 +1,24 @@
+/*
+* Copyright (C) 2021  Aravinth Manivannan <realaravinth@batsense.net>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+
+use actix_web::web::ServiceConfig;
+
+mod routes;
+
+pub fn services(cfg: &mut ServiceConfig) {
+    cfg.service(routes::login);
+}
diff --git a/src/templates/routes.rs b/src/templates/routes.rs
new file mode 100644
index 00000000..9278e632
--- /dev/null
+++ b/src/templates/routes.rs
@@ -0,0 +1,34 @@
+/*
+* Copyright (C) 2021  Aravinth Manivannan <realaravinth@batsense.net>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+
+use sailfish::TemplateOnce;
+
+#[derive(TemplateOnce, Default)]
+#[template(path = "signin.stpl")]
+struct SignIn;
+
+use actix_web::{get, post, web, HttpResponse, Responder};
+//use awc::Client;
+
+#[get("/login/")]
+pub async fn login() -> impl Responder {
+    let body = SignIn::default().render_once().unwrap();
+    //        .map_err(|_| ServiceError::InternalError)?;
+    HttpResponse::Ok()
+        .content_type("text/html; charset=utf-8")
+        .body(body)
+}
diff --git a/static/img/icon-trans.png b/static/img/icon-trans.png
new file mode 100644
index 00000000..db8da876
Binary files /dev/null and b/static/img/icon-trans.png differ
diff --git a/static/img/icon.png b/static/img/icon.png
new file mode 100644
index 00000000..75bf7879
Binary files /dev/null and b/static/img/icon.png differ
diff --git a/static/login/index.html b/static/login/index.html
new file mode 100644
index 00000000..e3ff0f76
--- /dev/null
+++ b/static/login/index.html
@@ -0,0 +1,142 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Login | mCaptcha</title>
+  </head>
+  <body>
+    <img src="./img/icon-trans.png" class="form__logo" alt="" />
+    <h2 class="form__brand">Sign in to mCaptcha</h2>
+
+    <div class="form-container">
+      <form class="form__box">
+        <label class="form__in-group" for="username"
+          >Username
+          <input
+            class="form__in-field"
+            type="text"
+            name="username"
+            id="username"
+            required
+          />
+        </label>
+
+        <label for="password" class="form__in-group"
+          >Password
+          <input
+            class="form__in-field"
+            type="password"
+            name="password"
+            id="password"
+            required
+          />
+          <a class="form__pw-recovery" -href="/forgot/password"
+            >Forgot password?</a
+          >
+        </label>
+        <button class="form__submit-button" type="submit">
+          Submit
+        </button>
+      </form>
+      <div class="form__secondary-action">
+        <p class="form__secondary-action__banner">New to mCaptcha? <a href="/signup" class="form__secondary-action__link">Create account</a></p>
+      </div>
+    </div>
+  </body>
+  <style>
+    * {
+      padding: 0;
+      margin: 0;
+    }
+
+    .form__logo {
+      width: 120px;
+      padding-top: 50px;
+      display: block;
+      margin: auto;
+    }
+
+    .form__brand {
+      padding: 10px 0;
+      text-align: center;
+    }
+
+
+    .form-container {
+      max-width: 40%;
+      min-width: 20%;
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -49.9%);
+      box-sizing: border-box;
+      margin: auto;
+      padding: 20px 0;
+    }
+
+    .form__box {
+      border: 1px solid #eaecef;
+      background-color: #f6f8fa;
+      border-radius: 5px;
+      padding: 20px 0;
+    }
+
+    .form__in-group {
+      display: block;
+      position: relative;
+      margin: auto;
+      max-width: 80%;
+      padding: 10px 0px;
+
+      box-sizing: content-box;
+
+      align-items: center;
+      align-content: center;
+    }
+
+    .form__in-field {
+      display: block;
+      box-sizing: border-box;
+      margin: 10px 0;
+      padding: 10px 0;
+      width: 100%;
+    }
+
+    .form__pw-recovery {
+      text-decoration: none;
+      color: rgb(3, 102, 214);
+      font-size: 0.8rem;
+    }
+
+    .form__submit-button {
+      display: block;
+      border: 1px solid skyblue;
+      background: #2ea44f;
+      color: white;
+      height: 40px;
+      border-radius: 5px;
+      width: 80%;
+      margin: auto;
+    }
+
+
+    .form__secondary-action {
+      display: block;
+      margin-top: 10px;
+    }
+
+    .form__secondary-action__banner {
+      display: block;
+      margin: auto;
+      max-width: 80%;
+      text-align: center;
+    }
+
+    .form__secondary-action__link{
+      text-decoration: none;
+      color: rgb(3, 102, 214);
+    }
+
+  </style>
+</html>
diff --git a/templates/bottom.stpl b/templates/bottom.stpl
new file mode 100644
index 00000000..0e890f28
--- /dev/null
+++ b/templates/bottom.stpl
@@ -0,0 +1,4 @@
+</body>
+  <script src="/index.js"></script>
+  <link rel="stylesheet" href="main.css" />
+</html>
diff --git a/templates/signin.stpl b/templates/signin.stpl
new file mode 100644
index 00000000..c9fffdf3
--- /dev/null
+++ b/templates/signin.stpl
@@ -0,0 +1,138 @@
+<% include!("./top.stpl"); %>
+
+<title>Sign in | mCaptcha</title>
+<img src="./img/icon-trans.png" class="form__logo" alt="" />
+    <h2 class="form__brand">Sign in to mCaptcha</h2>
+
+    <div class="form-container">
+      <form class="form__box">
+        <label class="form__in-group" for="username"
+          >Username
+          <input
+            class="form__in-field"
+            type="text"
+            name="username"
+            id="username"
+            required
+          />
+        </label>
+
+        <label for="password" class="form__in-group"
+          >Password
+          <input
+            class="form__in-field"
+            type="password"
+            name="password"
+            id="password"
+            required
+          />
+          <a class="form__pw-recovery" -href="/forgot/password"
+            >Forgot password?</a
+          >
+        </label>
+        <button class="form__submit-button" type="submit">
+          Submit
+        </button>
+      </form>
+      <div class="form__secondary-action">
+        <p class="form__secondary-action__banner">New to mCaptcha? <a href="/signup" class="form__secondary-action__link">Create account</a></p>
+      </div>
+    </div>
+  </body>
+  <style>
+    * {
+      padding: 0;
+      margin: 0;
+    }
+
+    .form__logo {
+      width: 120px;
+      padding-top: 50px;
+      display: block;
+      margin: auto;
+    }
+
+    .form__brand {
+      padding: 10px 0;
+      text-align: center;
+    }
+
+
+    .form-container {
+      max-width: 40%;
+      min-width: 20%;
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -49.9%);
+      box-sizing: border-box;
+      margin: auto;
+      padding: 20px 0;
+    }
+
+    .form__box {
+      border: 1px solid #eaecef;
+      background-color: #f6f8fa;
+      border-radius: 5px;
+      padding: 20px 0;
+    }
+
+    .form__in-group {
+      display: block;
+      position: relative;
+      margin: auto;
+      max-width: 80%;
+      padding: 10px 0px;
+
+      box-sizing: content-box;
+
+      align-items: center;
+      align-content: center;
+    }
+
+    .form__in-field {
+      display: block;
+      box-sizing: border-box;
+      margin: 10px 0;
+      padding: 10px 0;
+      width: 100%;
+    }
+
+    .form__pw-recovery {
+      text-decoration: none;
+      color: rgb(3, 102, 214);
+      font-size: 0.8rem;
+    }
+
+    .form__submit-button {
+      display: block;
+      border: 1px solid skyblue;
+      background: #2ea44f;
+      color: white;
+      height: 40px;
+      border-radius: 5px;
+      width: 80%;
+      margin: auto;
+    }
+
+
+    .form__secondary-action {
+      display: block;
+      margin-top: 10px;
+    }
+
+    .form__secondary-action__banner {
+      display: block;
+      margin: auto;
+      max-width: 80%;
+      text-align: center;
+    }
+
+    .form__secondary-action__link{
+      text-decoration: none;
+      color: rgb(3, 102, 214);
+    }
+
+  </style>
+
+<% include!("./bottom.stpl"); %>
diff --git a/templates/top.stpl b/templates/top.stpl
new file mode 100644
index 00000000..fa3239a1
--- /dev/null
+++ b/templates/top.stpl
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+