diff --git a/lib/Hikaru.hs b/lib/Hikaru.hs
index 8233e5ba18dcaf7cd478923ff6f4e4d8fcd950f6..7556292089e3d31566eca1596e409eb507b38bd8 100644
--- a/lib/Hikaru.hs
+++ b/lib/Hikaru.hs
@@ -14,6 +14,7 @@ module Hikaru
   (
   -- * Exported Modules
     module Hikaru.Action
+  , module Hikaru.CSRF
   , module Hikaru.Dispatch
   , module Hikaru.Form
   , module Hikaru.Link
@@ -24,6 +25,7 @@ module Hikaru
   )
 where
   import Hikaru.Action
+  import Hikaru.CSRF
   import Hikaru.Dispatch
   import Hikaru.Form
   import Hikaru.Link
diff --git a/lib/Hikaru/Form.hs b/lib/Hikaru/Form.hs
index e1153f81d39c78bdd374ecebc6414fcb5787f454..b1257856154041b153269f1bbbece054c4d7ee37 100644
--- a/lib/Hikaru/Form.hs
+++ b/lib/Hikaru/Form.hs
@@ -47,6 +47,8 @@ module Hikaru.Form
   -- ** Elements
   , element
   , hidden
+  , hiddenValue
+  , token
 
   -- ** Controls
   , ElementT
@@ -68,6 +70,9 @@ module Hikaru.Form
   , check
   , note
 
+  -- ** Value Adjustment
+  , adjust
+
   -- ** Rendering Hints
   , hint
 
@@ -98,6 +103,7 @@ where
   import Control.Monad.Trans (MonadTrans, lift)
   import Data.Text (Text, strip)
   import Hikaru.Action
+  import Hikaru.CSRF
   import Hikaru.Localize
   import Hikaru.Types
 
@@ -175,8 +181,7 @@ where
   -- |
   -- A form element that normally corresponds to a single row.
   --
-  -- An element can be either visible or hidden and can have multiple
-  -- controls. Hidden elements can have only 'input' controls.
+  -- Elements can have multiple controls.
   --
   data Element l
     = Element
@@ -372,11 +377,13 @@ where
   --
   data FormMessage
     = FormMsgFieldRequired
+    | FormMsgTokenInvalid
     deriving (Show)
 
   instance Localizable FormMessage where
     -- English strings
     localize "en" FormMsgFieldRequired = Just "This field is required."
+    localize "en" FormMsgTokenInvalid  = Just "The form has expired. Try again."
 
     -- No translation, caller should try a different locale.
     localize _lang _msg = Nothing
@@ -453,7 +460,11 @@ where
                    , envValidate = True
                    }
 
-    runStateT (flip runReaderT env runFormT) view
+    (res, view') <- runStateT (flip runReaderT env runFormT) view
+
+    if validates view'
+       then return (res, view')
+       else return (Nothing, view')
 
 
   -- |
@@ -496,7 +507,11 @@ where
                    , envValidate = True
                    }
 
-    runStateT (flip runReaderT env runFormT) view
+    (res, view') <- runStateT (flip runReaderT env runFormT) view
+
+    if validates view'
+       then return (res, view')
+       else return (Nothing, view')
 
 
   -- |
@@ -520,7 +535,11 @@ where
                    , envValidate = True
                    }
 
-    runStateT (flip runReaderT env runFormT) view
+    (res, view') <- runStateT (flip runReaderT env runFormT) view
+
+    if validates view'
+       then return (res, view')
+       else return (Nothing, view')
 
 
   -- Building Forms ----------------------------------------------------------
@@ -549,7 +568,7 @@ where
   -- |
   -- Add a new hidden form control.
   --
-  -- TODO
+  -- TODO: Add an example.
   --
   hidden :: (Monad m, FromFormMessage l, Param v)
          => Text
@@ -558,7 +577,35 @@ where
          -> FormT l o m v
   hidden name getter body = FormT do
     env@Env{..} <- ask
-    (val, text) <- getParamOrig name (getter <$> envValue)
+    (val, text) <- getParamOrig name (return $ getter <$> envValue)
+
+    new <- lift $ lift do
+      let field = InputField "hidden" Nothing text
+          state = ControlState name field [] [] val
+       in flip execStateT state $ flip runReaderT env $ runControlT body
+
+    ctrl <- lift $ lift $ buildControl env new
+
+    modify \view@View{..} ->
+      view { viewControls = viewControls <> [ctrl] }
+
+    return $ csValue new
+
+
+  -- |
+  -- Add a new hidden form control with a value completely uncoupled
+  -- from the form object. Useful for anti-CSRF tokens and similar.
+  --
+  -- TODO: Add an example
+  --
+  hiddenValue :: (Monad m, FromFormMessage l, Param v)
+              => Text
+              -> m (Maybe v)
+              -> ControlT 'InputFieldTag l o v m a
+              -> FormT l o m v
+  hiddenValue name generate body = FormT do
+    env@Env{..} <- ask
+    (val, text) <- getParamOrig name generate
 
     new <- lift $ lift do
       let field = InputField "hidden" Nothing text
@@ -573,6 +620,26 @@ where
     return $ csValue new
 
 
+  -- |
+  -- A hidden input holding an anti-CSRF token.
+  --
+  -- TODO: Add an example.
+  --
+  token :: (MonadCsrf m, FromFormMessage l) => Text -> FormT l o m Text
+  token name =
+    hiddenValue name (Just <$> generateToken) do
+      validate \case
+        Nothing -> do
+          return [Note Danger (fromFormMessage FormMsgTokenInvalid)]
+
+        Just tok -> do
+          valid <- isTokenValid tok
+          return
+            if valid
+               then []
+               else [Note Danger (fromFormMessage FormMsgTokenInvalid)]
+
+
   -- |
   -- TODO
   --
@@ -583,7 +650,7 @@ where
         -> ElementT l o m v
   input name getter body = ElementT do
     env@Env{..} <- ask
-    (val, text) <- getParamOrig name (getter <$> envValue)
+    (val, text) <- getParamOrig name (return $ getter <$> envValue)
 
     new <- lift $ lift do
       let field = InputField "text" Nothing text
@@ -710,6 +777,22 @@ where
       s { csHints = csHints <> [toDyn x] }
 
 
+  -- |
+  -- Adjust control value.
+  --
+  adjust :: (Monad m, Param v)
+         => (Maybe v -> m (Maybe v))
+         -> ControlT 'InputFieldTag l o v m ()
+  adjust fn = ControlT do
+    ControlState{csValue} <- get
+    value' <- lift $ lift $ fn csValue
+
+    modify \s@ControlState{csField} ->
+      s { csValue = value'
+        , csField = csField { fieldValue = maybe "" toParam value' }
+        }
+
+
   -- Debugging ---------------------------------------------------------------
 
 
@@ -736,22 +819,26 @@ where
   -- Internal ----------------------------------------------------------------
 
 
-  getParamOrig :: (Monad m, Param v)
-               => Text -> Maybe v -> ReaderT (Env o) m (Maybe v, Text)
-  getParamOrig name orig = do
+  getParamOrig :: (Monad m, Monad (n m), MonadTrans n, Param v)
+               => Text -> m (Maybe v) -> ReaderT (Env o) (n m) (Maybe v, Text)
+  getParamOrig name generate = do
     Env{..} <- ask
 
     case envParams of
-      Nothing -> return (orig, maybe "" toParam orig)
-      Just ps -> let param = strip <$> lookup (envPrefix <> "." <> name) ps
-                  in return (fromParam =<< param, fromMaybe "" param)
+      Nothing -> do
+        orig <- lift $ lift $ generate
+        return (orig, maybe "" toParam orig)
+
+      Just ps -> do
+        let param = strip <$> lookup (envPrefix <> "__" <> name) ps
+         in return (fromParam =<< param, fromMaybe "" param)
 
 
   getSelectParams :: (Monad m, Param v, Selectable v)
                   => Text -> ReaderT (Env o) m (Maybe v)
   getSelectParams name = do
     Env{..} <- ask
-    let name' = envPrefix <> "." <> name
+    let name' = envPrefix <> "__" <> name
     return $ selectValues . lookupList name' =<< envParams
 
 
@@ -768,7 +855,7 @@ where
               True  -> sequence $ map ($ csValue) (required : csValidators)
               False -> return []
 
-    return Control { ctrlName       = envPrefix <> "." <> csName
+    return Control { ctrlName       = envPrefix <> "__" <> csName
                    , ctrlField      = csField
                    , ctrlNotes      = mconcat vres
                    , ctrlHints      = csHints
@@ -780,4 +867,15 @@ where
   required _else   = return []
 
 
+  -- |
+  -- Returns True only if no view notes are of the 'Danger' severity.
+  --
+  validates :: View l -> Bool
+  validates View{..} = all elemOk viewElements && all ctrlOk viewControls
+    where
+      elemOk Element{..} = all ctrlOk elemControls
+      ctrlOk Control{..} = all noteOk ctrlNotes
+      noteOk Note{..} = noteSeverity /= Danger
+
+
 -- vim:set ft=haskell sw=2 ts=2 et: