diff --git a/Cargo.lock b/Cargo.lock
index 184b30b..8fa1c88 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5786,6 +5786,16 @@ dependencies = [
"shield-dioxus",
]
+[[package]]
+name = "shield-dummy"
+version = "0.1.0"
+dependencies = [
+ "async-trait",
+ "serde",
+ "serde_json",
+ "shield",
+]
+
[[package]]
name = "shield-email"
version = "0.1.0"
diff --git a/packages/methods/shield-credentials/src/provider.rs b/packages/methods/shield-credentials/src/provider.rs
index f764e6f..38ef51a 100644
--- a/packages/methods/shield-credentials/src/provider.rs
+++ b/packages/methods/shield-credentials/src/provider.rs
@@ -1,6 +1,6 @@
use shield::Provider;
-use crate::CREDENTIALS_METHOD_ID;
+use crate::method::CREDENTIALS_METHOD_ID;
pub struct CredentialsProvider;
diff --git a/packages/methods/shield-dummy/Cargo.toml b/packages/methods/shield-dummy/Cargo.toml
new file mode 100644
index 0000000..2cbf4aa
--- /dev/null
+++ b/packages/methods/shield-dummy/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "shield-dummy"
+description = "Dummy method for Shield."
+
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+version.workspace = true
+
+[dependencies]
+async-trait.workspace = true
+serde.workspace = true
+serde_json.workspace = true
+shield.workspace = true
diff --git a/packages/methods/shield-dummy/README.md b/packages/methods/shield-dummy/README.md
new file mode 100644
index 0000000..9905cb0
--- /dev/null
+++ b/packages/methods/shield-dummy/README.md
@@ -0,0 +1,13 @@
+
Shield Dummy
+
+Dummy method for Shield.
+
+## Documentation
+
+See [the Shield book](https://shield.rustforweb.org/) for documentation.
+
+## Rust for Web
+
+The Shield project is part of [Rust for Web](https://github.com/RustForWeb).
+
+[Rust for Web](https://github.com/RustForWeb) creates and ports web libraries for Rust. All projects are free and open source.
diff --git a/packages/methods/shield-dummy/src/actions.rs b/packages/methods/shield-dummy/src/actions.rs
new file mode 100644
index 0000000..757615c
--- /dev/null
+++ b/packages/methods/shield-dummy/src/actions.rs
@@ -0,0 +1,5 @@
+mod sign_in;
+mod sign_out;
+
+pub use sign_in::*;
+pub use sign_out::*;
diff --git a/packages/methods/shield-dummy/src/actions/sign_in.rs b/packages/methods/shield-dummy/src/actions/sign_in.rs
new file mode 100644
index 0000000..5ba22c4
--- /dev/null
+++ b/packages/methods/shield-dummy/src/actions/sign_in.rs
@@ -0,0 +1,86 @@
+use std::sync::Arc;
+
+use async_trait::async_trait;
+use serde::Deserialize;
+use shield::{
+ Action, ActionMethod, Form, Input, InputType, InputTypeText, MethodSession, Request, Response,
+ ResponseType, SessionAction, ShieldError, SignInAction, Storage, User, erased_action,
+};
+
+use crate::provider::DummyProvider;
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct SignInData {
+ pub user_id: String,
+}
+
+pub struct DummySignInAction {
+ storage: Arc>,
+}
+
+impl DummySignInAction {
+ pub fn new(storage: Arc>) -> Self {
+ Self { storage }
+ }
+}
+
+#[async_trait]
+impl Action for DummySignInAction {
+ fn id(&self) -> String {
+ SignInAction::id()
+ }
+
+ fn name(&self) -> String {
+ SignInAction::name()
+ }
+
+ fn openapi_summary(&self) -> &'static str {
+ "Sign in with dummy"
+ }
+
+ fn openapi_description(&self) -> &'static str {
+ "Sign in with dummy."
+ }
+
+ fn method(&self) -> ActionMethod {
+ ActionMethod::Post
+ }
+
+ async fn forms(&self, _provider: DummyProvider) -> Result, ShieldError> {
+ Ok(vec![Form {
+ inputs: vec![Input {
+ name: "userId".to_owned(),
+ label: Some("User ID".to_owned()),
+ r#type: InputType::Text(InputTypeText {
+ placeholder: Some("User ID".to_owned()),
+ required: Some(true),
+ ..Default::default()
+ }),
+ value: None,
+ addon_start: None,
+ addon_end: None,
+ }],
+ }])
+ }
+
+ async fn call(
+ &self,
+ _provider: DummyProvider,
+ _session: &MethodSession<()>,
+ request: Request,
+ ) -> Result {
+ let data = serde_json::from_value::(request.form_data)
+ .map_err(|err| ShieldError::Validation(err.to_string()))?;
+
+ let user = self
+ .storage
+ .user_by_id(&data.user_id)
+ .await?
+ .ok_or_else(|| ShieldError::Validation("User not found.".to_owned()))?;
+
+ Ok(Response::new(ResponseType::Default).session_action(SessionAction::authenticate(user)))
+ }
+}
+
+erased_action!(DummySignInAction, );
diff --git a/packages/methods/shield-dummy/src/actions/sign_out.rs b/packages/methods/shield-dummy/src/actions/sign_out.rs
new file mode 100644
index 0000000..b1ae478
--- /dev/null
+++ b/packages/methods/shield-dummy/src/actions/sign_out.rs
@@ -0,0 +1,55 @@
+use async_trait::async_trait;
+use shield::{
+ Action, ActionMethod, Form, MethodSession, Request, Response, ResponseType, SessionAction,
+ ShieldError, SignOutAction, erased_action,
+};
+
+use crate::provider::DummyProvider;
+
+pub struct DummySignOutAction;
+
+#[async_trait]
+impl Action for DummySignOutAction {
+ fn id(&self) -> String {
+ SignOutAction::id()
+ }
+
+ fn name(&self) -> String {
+ SignOutAction::name()
+ }
+
+ fn openapi_summary(&self) -> &'static str {
+ "Sign out with dummy"
+ }
+
+ fn openapi_description(&self) -> &'static str {
+ "Sign out with dummy."
+ }
+
+ fn method(&self) -> ActionMethod {
+ ActionMethod::Post
+ }
+
+ fn condition(
+ &self,
+ provider: &DummyProvider,
+ session: &MethodSession<()>,
+ ) -> Result {
+ SignOutAction::condition(provider, session)
+ }
+
+ async fn forms(&self, provider: DummyProvider) -> Result, ShieldError> {
+ SignOutAction::forms(provider).await
+ }
+
+ async fn call(
+ &self,
+ _provider: DummyProvider,
+ _session: &MethodSession<()>,
+ _request: Request,
+ ) -> Result {
+ Ok(Response::new(ResponseType::Default).session_action(SessionAction::Unauthenticate))
+ }
+}
+
+erased_action!(DummySignOutAction);
diff --git a/packages/methods/shield-dummy/src/lib.rs b/packages/methods/shield-dummy/src/lib.rs
new file mode 100644
index 0000000..5329836
--- /dev/null
+++ b/packages/methods/shield-dummy/src/lib.rs
@@ -0,0 +1,5 @@
+mod actions;
+mod method;
+mod provider;
+
+pub use method::*;
diff --git a/packages/methods/shield-dummy/src/method.rs b/packages/methods/shield-dummy/src/method.rs
new file mode 100644
index 0000000..50cf812
--- /dev/null
+++ b/packages/methods/shield-dummy/src/method.rs
@@ -0,0 +1,46 @@
+use std::sync::Arc;
+
+use async_trait::async_trait;
+use shield::{Action, Method, ShieldError, Storage, User, erased_method};
+
+use crate::{
+ actions::{DummySignInAction, DummySignOutAction},
+ provider::DummyProvider,
+};
+
+pub const DUMMY_METHOD_ID: &str = "dummy";
+
+pub struct DummyMethod {
+ storage: Arc>,
+}
+
+impl DummyMethod {
+ pub fn new + 'static>(storage: S) -> Self {
+ Self {
+ storage: Arc::new(storage),
+ }
+ }
+}
+
+#[async_trait]
+impl Method for DummyMethod {
+ type Provider = DummyProvider;
+ type Session = ();
+
+ fn id(&self) -> String {
+ DUMMY_METHOD_ID.to_owned()
+ }
+
+ fn actions(&self) -> Vec>> {
+ vec![
+ Box::new(DummySignInAction::new(self.storage.clone())),
+ Box::new(DummySignOutAction),
+ ]
+ }
+
+ async fn providers(&self) -> Result, ShieldError> {
+ Ok(vec![DummyProvider])
+ }
+}
+
+erased_method!(DummyMethod, );
diff --git a/packages/methods/shield-dummy/src/provider.rs b/packages/methods/shield-dummy/src/provider.rs
new file mode 100644
index 0000000..b3b2140
--- /dev/null
+++ b/packages/methods/shield-dummy/src/provider.rs
@@ -0,0 +1,19 @@
+use shield::Provider;
+
+use crate::method::DUMMY_METHOD_ID;
+
+pub struct DummyProvider;
+
+impl Provider for DummyProvider {
+ fn method_id(&self) -> String {
+ DUMMY_METHOD_ID.to_owned()
+ }
+
+ fn id(&self) -> Option {
+ None
+ }
+
+ fn name(&self) -> String {
+ "Dummy".to_owned()
+ }
+}