diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,7 +1,20 @@
+0.9.8
+=====
+
+* If the user is not authenticatedi, show login page with [HTTP status code
+  511](https://tools.ietf.org/html/rfc6585), instead of 302 -> 200.  It had
+  bad UX for AJAX calls.
+* Always convert authenticated user's email to lowercase. This affects the
+  cookie and the `From` header.
+* Stop using the `string-conversions` package.
+* Print authentication code in debug mode only.
+
+
 0.9.7.1
 =======
 
 * Fixed cabal source distribution
+
 
 0.9.7
 =====
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -12,14 +12,14 @@
 
 ## How it Works
 
-When an HTTP client makes a request, Sproxy checks for a *session
-cookie*.  If it doesn't exist (or it's invalid, expired), it redirects
-the client to the login page, where the user can choose
-[OAuth2](https://tools.ietf.org/html/rfc6749) provider to authenticate with.
-Finally, we store the the email address in a session cookie: signed with a
-hash to prevent tampering, set for HTTP only (to prevent malicious JavaScript
-from reading it), and set it for secure (since we don't want it traveling
-over plaintext HTTP connections).
+When an HTTP client makes a request, Sproxy checks for a *session cookie*.
+If it doesn't exist (or it's invalid, expired), it responses with [HTTP
+status 511](https://tools.ietf.org/html/rfc6585) with the page, where the
+user can choose an [OAuth2](https://tools.ietf.org/html/rfc6749) provider to
+authenticate with.  Finally, we store the the email address in a session
+cookie: signed with a hash to prevent tampering, set for HTTP only (to prevent
+malicious JavaScript from reading it), and set it for secure (since we don't
+want it traveling over plaintext HTTP connections).
 
 From that point on, when sproxy detects a valid session cookie it extracts the
 email, checks it against the access rules, and relays the request to the
diff --git a/sproxy.cabal b/sproxy.cabal
--- a/sproxy.cabal
+++ b/sproxy.cabal
@@ -1,5 +1,5 @@
 name: sproxy
-version: 0.9.7.1
+version: 0.9.8
 synopsis: HTTP proxy for authenticating users via OAuth2
 license: MIT
 license-file: LICENSE
@@ -54,10 +54,8 @@
     , interpolatedstring-perl6
     , network
     , postgresql-simple
-    , raw-strings-qq >= 1.1
     , resource-pool
     , split
-    , string-conversions
     , text
     , time
     , tls >= 1.3.3
diff --git a/src/Authenticate.hs b/src/Authenticate.hs
--- a/src/Authenticate.hs
+++ b/src/Authenticate.hs
@@ -4,15 +4,13 @@
 
 module Authenticate (
   logout
-, loginPage
-, redirectToLoginPage
+, authenticationRequired
 , validAuth
 ) where
 
 import Data.ByteString (ByteString)
-import Data.Monoid ((<>))
 import Network.HTTP.Toolkit (Response, BodyReader)
-import Network.HTTP.Types (found302, ok200, urlEncode)
+import Network.HTTP.Types (found302, Status(..))
 import System.Posix.Time (epochTime)
 import Text.InterpolatedString.Perl6 (qc)
 import Text.Read (readMaybe)
@@ -25,15 +23,9 @@
 import qualified Authenticate.Google as Google
 import qualified Authenticate.LinkedIn as LinkedIn
 
-redirectToLoginPage :: ByteString -> ByteString -> IO (Response BodyReader)
-redirectToLoginPage base path =
-  mkResponse found302 [
-    ("Location", base <> "/sproxy/login?state=" <> urlEncode True path)
-  ] ""
-
-loginPage :: AuthConfig -> ByteString -> ByteString -> IO (Response BodyReader)
-loginPage c base path =
-  mkHtmlResponse ok200 body
+authenticationRequired :: AuthConfig -> ByteString -> ByteString -> IO (Response BodyReader)
+authenticationRequired c base path =
+  mkHtmlResponse (Status 511 "Authentication Required") body
   where
     google :: ByteString
     google = maybe "" (\u -> [qc|<p><a href="{u}">Authenticate with Google</a></p>|]) (Google.authUrl c base path)
diff --git a/src/Authenticate/Google.hs b/src/Authenticate/Google.hs
--- a/src/Authenticate/Google.hs
+++ b/src/Authenticate/Google.hs
@@ -10,6 +10,7 @@
 import Control.Exception (try)
 import Data.Aeson (FromJSON, decode, parseJSON, Value(Object), (.:))
 import Data.ByteString (ByteString)
+import Data.Char (toLower)
 import Data.Maybe (fromJust)
 import Data.Monoid ((<>))
 import Network.HTTP.Toolkit (Response, BodyReader)
@@ -37,7 +38,7 @@
 
 authenticate :: AuthConfig -> ByteString -> ByteString -> ByteString -> IO (Response BodyReader)
 authenticate acfg base path code = do
-  Log.info ("Google authentication request with code " ++ show code)
+  Log.debug ("Google authentication request with code " ++ show code)
   tokenRes <- try $ post "https://accounts.google.com/o/oauth2/token"
               ("code=" <> code <> "&client_id=" <> clientId
                 <> "&client_secret=" <> clientSecret <> "&redirect_uri="
@@ -57,7 +58,7 @@
               case decode (HTTP.responseBody uresp) of
                 Nothing -> authenticationFailed "Received an invalid user info response from Google's authentication server."
                 Just (GoogleUserInfo{email, givenName, familyName}) -> do
-                  token <- mkAuthToken acfg AuthUser{ authUserEmail = email
+                  token <- mkAuthToken acfg AuthUser{ authUserEmail = map toLower email
                                                     , authUserGivenName = givenName
                                                     , authUserFamilyName = familyName }
                   let cookie = setCookie cookieDomain cookieName (show token) (authConfigShelfLife acfg)
diff --git a/src/Authenticate/LinkedIn.hs b/src/Authenticate/LinkedIn.hs
--- a/src/Authenticate/LinkedIn.hs
+++ b/src/Authenticate/LinkedIn.hs
@@ -10,6 +10,7 @@
 import Control.Exception (try)
 import Data.Aeson (FromJSON, decode, parseJSON, Value(Object), (.:))
 import Data.ByteString (ByteString)
+import Data.Char (toLower)
 import Data.Maybe (fromJust)
 import Data.Monoid ((<>))
 import Network.HTTP.Toolkit (Response, BodyReader)
@@ -37,7 +38,7 @@
 
 authenticate :: AuthConfig -> ByteString -> ByteString -> ByteString -> IO (Response BodyReader)
 authenticate acfg base path code = do
-  Log.info ("LinkedIn authentication request with code " ++ show code)
+  Log.debug ("LinkedIn authentication request with code " ++ show code)
   tokenRes <- try $ post "https://www.linkedin.com/oauth/v2/accessToken"
               ("code=" <> code <> "&client_id=" <> clientId
                 <> "&client_secret=" <> clientSecret <> "&redirect_uri="
@@ -59,7 +60,7 @@
               case decode (HTTP.responseBody uresp) of
                 Nothing -> authenticationFailed "Received an invalid user info response from LinkedIn authentication server."
                 Just (LinkedInUserInfo{emailAddress, firstName, lastName}) -> do
-                  token <- mkAuthToken acfg AuthUser{ authUserEmail = emailAddress
+                  token <- mkAuthToken acfg AuthUser{ authUserEmail = map toLower emailAddress
                                                     , authUserGivenName = firstName
                                                     , authUserFamilyName = lastName }
                   let cookie = setCookie cookieDomain cookieName (show token) (authConfigShelfLife acfg)
diff --git a/src/Authenticate/Types.hs b/src/Authenticate/Types.hs
--- a/src/Authenticate/Types.hs
+++ b/src/Authenticate/Types.hs
@@ -7,7 +7,7 @@
 ) where
 
 import Control.Applicative (empty)
-import Data.Aeson (FromJSON, parseJSON, Value(Object), (.:), (.:?))
+import Data.Aeson (FromJSON, parseJSON, Value(Object), (.:))
 import Data.ByteString (ByteString)
 import System.Posix.Types (EpochTime)
 
diff --git a/src/Authorize.hs b/src/Authorize.hs
--- a/src/Authorize.hs
+++ b/src/Authorize.hs
@@ -9,14 +9,14 @@
 , withDatabaseAuthorizeAction
 ) where
 
-import           Control.Exception
-import           Data.ByteString (ByteString)
-import           Network.HTTP.Types
-import           Text.InterpolatedString.Perl6 (q)
-import           Database.PostgreSQL.Simple
-import           Data.Pool
-import           Data.Time.Clock
-import           Data.String.Conversions (cs)
+import Control.Exception
+import Data.ByteString (ByteString)
+import Data.Pool
+import Data.Time.Clock
+import Database.PostgreSQL.Simple
+import Network.HTTP.Types
+import Text.InterpolatedString.Perl6 (q)
+import qualified Data.ByteString.Char8 as B8
 
 type AuthorizeAction = Email -> Domain -> RequestPath -> Method -> IO [Group]
 
@@ -31,7 +31,7 @@
 createConnectionPool database = createPool open close 1 connectionIdleTime connectionPoolSize
   where
     open :: IO Connection
-    open = connectPostgreSQL (cs database)
+    open = connectPostgreSQL (B8.pack database)
 
     connectionPoolSize :: Int
     connectionPoolSize = 5
diff --git a/src/Main.hs b/src/Main.hs
--- a/src/Main.hs
+++ b/src/Main.hs
@@ -8,7 +8,7 @@
 import Data.Version (showVersion)
 import Paths_sproxy (version) -- from cabal
 import System.Environment (getArgs)
-import Text.RawString.QQ (r)
+import Text.InterpolatedString.Perl6 (qc)
 import qualified System.Console.Docopt.NoTH as O
 
 import Authorize (withDatabaseAuthorizeAction)
@@ -17,7 +17,7 @@
 
 usage :: String
 usage =  "SProxy " ++ showVersion version ++
-  " HTTP proxy for authenticating users via OAuth2" ++ [r|
+  " HTTP proxy for authenticating users via OAuth2" ++ [qc|
 
 Usage:
   sproxy [options]
diff --git a/src/Proxy.hs b/src/Proxy.hs
--- a/src/Proxy.hs
+++ b/src/Proxy.hs
@@ -14,7 +14,6 @@
 import Data.Map as Map (fromListWith, toList, insert, delete)
 import Data.Maybe
 import Data.Monoid
-import Data.String.Conversions (cs)
 import GHC.IO.Exception
 import Network (PortID(..), connectTo)
 import Network.HTTP.Toolkit
@@ -39,7 +38,7 @@
 import qualified Network.TLS as TLS
 import qualified Network.TLS.Extra as TLS
 
-import Authenticate (loginPage, logout, redirectToLoginPage, validAuth)
+import Authenticate (logout, authenticationRequired, validAuth)
 import Authenticate.Token (AuthToken(..), AuthUser(..))
 import Authenticate.Types (AuthConfig(..), OAuthClient(..))
 import Authorize
@@ -203,16 +202,15 @@
                   case join $ lookup "code" query of
                     Nothing -> Just <$> badRequest
                     Just code -> Just <$> LinkedIn.authenticate authConfig base redirectPath code
-                ["sproxy", "login"] -> Just <$> loginPage authConfig base redirectPath
                 ["sproxy", "logout"] -> Just <$> logout authConfig (base <> redirectPath)
                 ["robots.txt"] -> Just <$> mkTextResponse ok200 "User-agent: *\nDisallow: /"
                 _ -> -- Check for an auth cookie.
                   case removeCookie (authConfigCookieName authConfig) (parseCookies headers) of
-                    Nothing -> Just <$> redirectToLoginPage base path
+                    Nothing -> Just <$> authenticationRequired authConfig base path
                     Just (authCookie, cookies) -> do
                       auth <- validAuth authConfig authCookie
                       case auth of
-                        Nothing -> Just <$> redirectToLoginPage base path
+                        Nothing -> Just <$> authenticationRequired authConfig base path
                         Just token ->
                           forwardRequest config send authorize cookies addr request token
           forM_ mResponse (sendResponse send)
@@ -228,7 +226,9 @@
                -> AuthToken
                -> IO (Maybe (Response BodyReader))
 forwardRequest config send authorize cookies addr request@(Request method path headers _) token = do
-    groups <- authorize (authUserEmail . authUser $ token) (maybe (error "No Host") cs $ lookup "Host" headers) path method
+    groups <- authorize (authUserEmail . authUser $ token)
+                        (fromMaybe (error "No Host") $ lookup "Host" headers)
+                        path method
     ip <- formatSockAddr addr
     case groups of
         [] -> Just <$> accessDenied (authUserEmail . authUser $ token)
@@ -236,10 +236,10 @@
             -- TODO: Reuse connections to the backend server.
             let downStreamHeaders =
                     toList $
-                    insert "From" (cs . authUserEmail . authUser $ token) $
-                    insert "X-Groups" (cs $ intercalate "," groups) $
-                    insert "X-Given-Name" (cs . authUserGivenName . authUser $ token) $
-                    insert "X-Family-Name" (cs . authUserFamilyName . authUser $ token) $
+                    insert "From" (B8.pack . authUserEmail . authUser $ token) $
+                    insert "X-Groups" (B8.pack $ intercalate "," groups) $
+                    insert "X-Given-Name" (B8.pack . authUserGivenName . authUser $ token) $
+                    insert "X-Family-Name" (B8.pack . authUserFamilyName . authUser $ token) $
                     insert "X-Forwarded-Proto" "https" $
                     addForwardedForHeader ip $
                     insert "Connection" "close" $
