How to implement recaptcha 2.0 on ASP.NET? - asp.net

I want to implement recaptcha 2.0 on my web page. I followed the steps from there by putting on client side:
<script src='https://www.google.com/recaptcha/api.js'></script>
and:
<div class="g-recaptcha" data-sitekey="my_data-sitekey"></div>
but, as far I understood, that's not enough. There is also something which must be done on server side. What and how should I do?

I made a simple and easy to use implementation.
Add the below class to your web project.
using System.Linq;
using System.Net.Http;
using Abp.Threading;
using Abp.Web.Models;
using Abp.Web.Mvc.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Newtonsoft.Json;
namespace WebDemo.Web.Attributes
{
public class ValidateRecaptchaAttribute : ActionFilterAttribute
{
private readonly string _propertyName;
private readonly string _secretKey;
private readonly string _errorViewName;
private readonly string _errorMessage;
private const string GoogleRecaptchaUrl = "https://www.google.com/recaptcha/api/siteverify?secret={0}&response={1}";
private const string SecretKey = "***YOUR PRIVATE KEY HERE***";
public ValidateRecaptchaAttribute(string propertyName = "RepatchaValue", string secretKey = SecretKey, string errorViewName = "Error", string errorMessage = "Invalid captcha!")
{
_propertyName = propertyName;
_secretKey = secretKey;
_errorViewName = errorViewName;
_errorMessage = errorMessage;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
var model = context.ActionArguments.First().Value;
var propertyInfo = model.GetType().GetProperty(_propertyName);
if (propertyInfo != null)
{
var repatchaValue = propertyInfo.GetValue(model, null) as string;
var captchaValidationResult = ValidateRecaptcha(repatchaValue, _secretKey);
if (captchaValidationResult.Success)
{
base.OnActionExecuting(context);
return;
}
}
SetInvalidResult(context);
}
private void SetInvalidResult(ActionExecutingContext context)
{
var errorModel = new ErrorViewModel(new ErrorInfo(_errorMessage));
var viewResult = new ViewResult
{
ViewName = _errorViewName,
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary())
{
Model = errorModel
}
};
context.Result = viewResult;
}
private static RecaptchaResponse ValidateRecaptcha(string userEnteredCaptcha, string secretKey)
{
if (string.IsNullOrEmpty(userEnteredCaptcha))
{
return new RecaptchaResponse
{
Success = false,
ErrorCodes = new[] { "missing-input-response" }
};
}
using (var client = new HttpClient())
{
var result = AsyncHelper.RunSync<string>(() => client.GetStringAsync(string.Format((string)GoogleRecaptchaUrl, secretKey, userEnteredCaptcha)));
var captchaResponse = JsonConvert.DeserializeObject<RecaptchaResponse>(result);
return captchaResponse;
}
}
public class RecaptchaResponse
{
[JsonProperty("success")]
public bool Success { get; set; }
[JsonProperty("challenge_ts")]
public string ChallengeTs { get; set; } // timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)
[JsonProperty("hostname")]
public string Hostname { get; set; } // the hostname of the site where the reCAPTCHA was solved
[JsonProperty("error-codes")]
public string[] ErrorCodes { get; set; } // optional
}
}
}
And usage is very simple;
[HttpGet]
[ValidateRecaptcha]
public ActionResult CreateProject(CreateModel model)
{
//your main code that needs to be done after captcha validation.
}

You have to get an api in order to use this google service.
https://developers.google.com/recaptcha/docs/start

Here is the vb.net code to validate captcha on server side
Public Function ValidateCaptcha() As Boolean
Dim valid As Boolean = False
Dim Ressponse As String = Request("g-recaptcha-response")
Dim strKey As String = ConfigurationManager.AppSettings("google.recaptcha.secretkey")
Dim req As HttpWebRequest = Net.WebRequest.Create("https://www.google.com/recaptcha/api/siteverify?secret=" + strKey + "&response=" + Ressponse)
Try
Using wResponse As WebResponse = req.GetResponse()
Using readStream As New StreamReader(wResponse.GetResponseStream())
Dim jsonResponse As String = readStream.ReadToEnd()
Dim js As New JavaScriptSerializer()
Dim data As MyObject = js.Deserialize(Of MyObject)(jsonResponse)
' Deserialize Json
valid = Convert.ToBoolean(data.success)
End Using
End Using
Return valid
Catch ex As Exception
Throw ex
End Try
End Function
here is the class MYObject
Public Class MyObject
Public Property success() As String
Get
Return m_success
End Get
Set(ByVal value As String)
m_success = Value
End Set
End Property
Private m_success As String
End Class
And you need to call this ValidateCaptcha() function from your button click event as below:
Protected Sub btnTrial_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnTrial.Click
If ValidateCaptcha() Then
'''''Do your query here'''''
End If
End Sub
Please refer How to Validate Recaptcha V2 Server side to get more details

Related

Query Cosmos DB to get a list of different derived types using the .Net SDK Microsoft.Azure.Cosmos

We have an interface and a base class with multiple derived types.
public interface IEvent
{
[JsonProperty("id")]
public string Id { get; set; }
string Type { get; }
}
public abstract class EventBase: IEvent
{
public string Id { get; set; }
public abstract string Type { get; }
}
public class UserCreated : EventBase
{
public override string Type { get; } = typeof(UserCreated).AssemblyQualifiedName;
}
public class UserUpdated : EventBase
{
public override string Type { get; } = typeof(UserUpdated).AssemblyQualifiedName;
}
We are storing these events of different derived types in the same container in Cosmos DB using v3 of .Net SDK Microsoft.Azure.Cosmos. We then want to read all the events and have them deserialized to the correct type.
public class CosmosDbTests
{
[Fact]
public async Task TestFetchingDerivedTypes()
{
var endpoint = "";
var authKey = "";
var databaseId ="";
var containerId="";
var client = new CosmosClient(endpoint, authKey);
var container = client.GetContainer(databaseId, containerId);
await container.CreateItemAsync(new UserCreated{ Id = Guid.NewGuid().ToString() });
await container.CreateItemAsync(new UserUpdated{ Id = Guid.NewGuid().ToString() });
var queryable = container.GetItemLinqQueryable<IEvent>();
var query = queryable.ToFeedIterator();
var list = new List<IEvent>();
while (query.HasMoreResults)
{
list.AddRange(await query.ReadNextAsync());
}
Assert.NotEmpty(list);
}
}
Doesn't seem to be any option to tell GetItemLinqQueryable how to handle types. Is there any other method or approach to support multiple derived types in one query?
It's ok to put the events in some kind of wrapper entity if that would help, but they aren't allowed to be stored as an serialized sting inside a property.
The comment from Stephen Clearly pointed me in the right direction and with the help of this blog https://thomaslevesque.com/2019/10/15/handling-type-hierarchies-in-cosmos-db-part-2/ I ended up with a solution similar to the following example were we have a custom CosmosSerializer that uses a custom JsonConverter that reads the Type property.
public interface IEvent
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("$type")]
string Type { get; }
}
public abstract class EventBase: IEvent
{
public string Id { get; set; }
public string Type => GetType().AssemblyQualifiedName;
}
public class UserCreated : EventBase
{
}
public class UserUpdated : EventBase
{
}
EventJsonConverter reads the Type property.
public class EventJsonConverter : JsonConverter
{
// This converter handles only deserialization, not serialization.
public override bool CanRead => true;
public override bool CanWrite => false;
public override bool CanConvert(Type objectType)
{
// Only if the target type is the abstract base class
return objectType == typeof(IEvent);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// First, just read the JSON as a JObject
var obj = JObject.Load(reader);
// Then look at the $type property:
var typeName = obj["$type"]?.Value<string>();
return typeName == null ? null : obj.ToObject(Type.GetType(typeName), serializer);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotSupportedException("This converter handles only deserialization, not serialization.");
}
}
The NewtonsoftJsonCosmosSerializer takes a JsonSerializerSettings that it uses for serialization.
public class NewtonsoftJsonCosmosSerializer : CosmosSerializer
{
private static readonly Encoding DefaultEncoding = new UTF8Encoding(false, true);
private readonly JsonSerializer _serializer;
public NewtonsoftJsonCosmosSerializer(JsonSerializerSettings settings)
{
_serializer = JsonSerializer.Create(settings);
}
public override T FromStream<T>(Stream stream)
{
if (typeof(Stream).IsAssignableFrom(typeof(T)))
{
return (T)(object)stream;
}
using var sr = new StreamReader(stream);
using var jsonTextReader = new JsonTextReader(sr);
return _serializer.Deserialize<T>(jsonTextReader);
}
public override Stream ToStream<T>(T input)
{
var streamPayload = new MemoryStream();
using var streamWriter = new StreamWriter(streamPayload, encoding: DefaultEncoding, bufferSize: 1024, leaveOpen: true);
using JsonWriter writer = new JsonTextWriter(streamWriter);
writer.Formatting = _serializer.Formatting;
_serializer.Serialize(writer, input);
writer.Flush();
streamWriter.Flush();
streamPayload.Position = 0;
return streamPayload;
}
}
The CosmosClient is now created with our own NewtonsoftJsonCosmosSerializer and EventJsonConverter.
public class CosmosDbTests
{
[Fact]
public async Task TestFetchingDerivedTypes()
{
var endpoint = "";
var authKey = "";
var databaseId ="";
var containerId="";
var client = new CosmosClient(endpoint, authKey, new CosmosClientOptions
{
Serializer = new NewtonsoftJsonCosmosSerializer(new JsonSerializerSettings
{
Converters = { new EventJsonConverter() }
})
});
var container = client.GetContainer(databaseId, containerId);
await container.CreateItemAsync(new UserCreated{ Id = Guid.NewGuid().ToString() });
await container.CreateItemAsync(new UserUpdated{ Id = Guid.NewGuid().ToString() });
var queryable = container.GetItemLinqQueryable<IEvent>();
var query = queryable.ToFeedIterator();
var list = new List<IEvent>();
while (query.HasMoreResults)
{
list.AddRange(await query.ReadNextAsync());
}
Assert.NotEmpty(list);
}
}

Call Secure ASP.Net API from Windows 8 Phone App with WebClient

i cretaed my own windows phone app based on this touturial: http://www.asp.net/web-api/overview/mobile-clients/calling-web-api-from-a-windows-phone-8-application
So far everyting is working.
Now i secured my Controller with [Authentication].
When i call the API from a browser, i get redirected to a loginpage with forms Auth.
The same thing when i try to get the records from my phone app. Can anyone help me coding the webClient to the get all my data from the webapi with the Windows 8 Phone App?
Asp.net API Controller:
public class AlertsController : ApiController
{
private AlertRepository repository = null;
public AlertsController()
{
this.repository = new AlertRepository();
}
[HttpGet]
[Authorize]
public HttpResponseMessage Get()
{
IEnumerable<AlertDetails> alerts = this.repository.ReadAllAlerts();
if (alerts != null)
{
return Request.CreateResponse<IEnumerable<AlertDetails>>(HttpStatusCode.OK, alerts);
}
else
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
}
enter code here
The WebClient in MainViewModel from the PhoneApp:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using AlertCatalog.Resources;
using System.Net;
using AlertCatalog.Models;
using Newtonsoft.Json;
namespace AlertCatalog.ViewModels
{
public class MainViewModel : INotifyPropertyChanged
{
const string apiUrl = #"http://localhost:25518/api/Alerts";
public MainViewModel()
{
this.Items = new ObservableCollection<ItemViewModel>();
}
/// <summary>
/// A collection for ItemViewModel objects.
/// </summary>
public ObservableCollection<ItemViewModel> Items { get; private set; }
public bool IsDataLoaded
{
get;
private set;
}
/// <summary>
/// Creates and adds a few ItemViewModel objects into the Items collection.
/// </summary>
///
//Create an instance of your new CookieAware Web Client
public void LoadData()
{
if (this.IsDataLoaded == false)
{
this.Items.Clear();
this.Items.Add(new ItemViewModel() { ID = "0", LineOne = "Please Wait...", LineTwo = "Please wait while the catalog is downloaded from the server.", LineThree = null });
WebClient webClient = new WebClient();
webClient.Credentials = new NetworkCredential("testusername", "testpassword");
webClient.Headers["Accept"] = "application/json";
webClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(webClient_DownloadCatalogCompleted);
webClient.DownloadStringAsync(new Uri(apiUrl));
}
}
private void webClient_DownloadCatalogCompleted(object sender, DownloadStringCompletedEventArgs e)
{
try
{
this.Items.Clear();
if (e.Result != null)
{
var alerts = JsonConvert.DeserializeObject<AlertDetails[]>(e.Result);
int id = 0;
foreach (AlertDetails alert in alerts)
{
this.Items.Add(new ItemViewModel()
{
ID = (id++).ToString(),
LineOne = alert.Severity,
LineTwo = alert.Name,
LineThree = alert.Description.Replace("\n", " ")
});
}
this.IsDataLoaded = true;
}
}
catch (Exception ex)
{
this.Items.Add(new ItemViewModel()
{
ID = "0",
LineOne = "An Error Occurred",
LineTwo = String.Format("The following exception occured: {0}", ex.Message),
LineThree = String.Format("Additional inner exception information: {0}", ex.InnerException.Message)
});
}
}
ErrorMessage:
Error from PhoneApp
Simply adding webClient.Credentials = new NetworkCredential("testusername", "testpassword"); does not mean that your api will recognise the user as authorized.
You can derive from the AuthorizeAttributeto specify your own authorization rules.
e.g.
public class MyAuthAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//Implement your own checks here
//httpContext.Request.Headers["yourHeaderToCheck"]
return true;
}
}
Then replace [Authorize] with [MyAuth]

Json.net , JsonConvert gives me an error during deserialization: not generating an object instance

I’m sending from an asp.net page Json data from javascript, the JS data/object was previously serialized and the sent to my asp.net genericHandler.ashx page.
On server side I’m using Json.net, this is my code in the generic handler:
public void ProcessRequest(HttpContext context)
{
int numberOfFiles = 0;
MyGlobalSettings myGlobalSett = new MyGlobalSettings();
JavaScriptSerializer jsonSerializer = new JavaScriptSerializer();
string jsonString = string.Empty;
context.Request.InputStream.Position = 0;
using (StreamReader inputStream = new System.IO.StreamReader(context.Request.InputStream))
{
jsonString = inputStream.ReadToEnd();
}
MyData contentType = new MyData();
contentType = jsonSerializer.Deserialize<MyData>(jsonString);
Debug.WriteLine("Context 1:" + contentType.name);
context.Request.InputStream.Position = 0;
string json;
using (var reader = new StreamReader(context.Request.InputStream))
{
json = reader.ReadToEnd();
}
MyData contentType2 = new MyData();
contentType2=JsonConvert.DeserializeObject<MyData>(json);
Debug.WriteLine("Context 2:" + contentType2.name);
My deserialized object class
public class MyData
{
public string name { get; set; }
public string lastName { get; set; }
public string age { get; set; }
}
This is the error I get.
An exception of type 'System.NullReferenceException' occurred in Demo_xss_prevention_04.dll but was not handled in user code
Additional information: Object reference not set to an instance of an object.
I've tried the built in .net Json serializer (JavaScriptSerializer), and it works, but with Json.net I get this error. How can I fix it?

asp.net authentication of users best methods webforms

Whats the best way for authenticating web users and storing their details which i have a class for:
Should i use sessions or the forms auth cookie?
How can i access it from either way say like userclass.username?
I would like to store quite a lot of user information to stop database calls for stuff like: user type, user a full name, addresss, post code, foo1, foo2, foo3, foo4 and more. I know this could go in session or auth cookie user data. This question relates and links to https://stackoverflow.com/questions/18393122/whats-the-best-way-to-authenticate-a-user-and-store-user-details-sessions-or-fo which i have not had any help on
Really could do with some help and advice here as i have a few system which i need to do this for. Any comments appreciated.
thanks
************************************ Links *****************************
My code based roughly on:
http://www.shawnmclean.com/blog/2012/01/storing-strongly-typed-object-user-profile-data-in-asp-net-forms-authentication-cookie/
http://www.danharman.net/2011/07/07/storing-custom-data-in-forms-authentication-tickets/
************************************ EDIT *****************************
custom identity module
Public Module IdentityExtensions
Sub New()
End Sub
Private _CustomIdentityUser As CustomIdentityUser
<System.Runtime.CompilerServices.Extension> _
Public Function CustomIdentity(identity As System.Security.Principal.IIdentity) As CustomIdentityUser
'If _CustomIdentityUser Is Nothing Then
'_CustomIdentityUser = DirectCast(identity, CustomIdentityUser)
_CustomIdentityUser = Nothing
If identity.GetType = GetType(FormsIdentity) Then
_CustomIdentityUser = New CustomIdentityUser(DirectCast(identity, FormsIdentity).Ticket)
Else
If identity.IsAuthenticated Then
FormsAuthentication.RedirectToLoginPage()
End If
End If
Return _CustomIdentityUser
End Function
End Module
My Custom identity user
Public Class CustomIdentityUser
Implements System.Security.Principal.IIdentity
Private ticket As System.Web.Security.FormsAuthenticationTicket
Private _Auth As Auth
Public Sub New(ticket As System.Web.Security.FormsAuthenticationTicket)
Me.ticket = ticket
_Auth = New projectabc.Auth(Me.ticket)
End Sub
Public ReadOnly Property Auth As Auth
Get
Return Me._Auth
End Get
End Property
Public ReadOnly Property Username As String
Get
Return Auth.Username
End Get
End Property
Public ReadOnly Property UserType As Enumerations.EnumUserType
Get
Return Auth.UserType
End Get
End Property
Public ReadOnly Property OwnerType As Enumerations.EnumOwnerType
Get
Return Auth.OwnerType
End Get
End Property
Public ReadOnly Property AuthenticationType As String Implements System.Security.Principal.IIdentity.AuthenticationType
Get
Return "Custom"
End Get
End Property
Public ReadOnly Property IsAuthenticated As Boolean Implements System.Security.Principal.IIdentity.IsAuthenticated
Get
Return ticket IsNot Nothing
End Get
End Property
Public ReadOnly Property Name As String Implements System.Security.Principal.IIdentity.Name
Get
Return Username
End Get
End Property
End Class
Then as you can see the user class calls an auth class which basically has all the properties for the user and gets and set it etc.
Public Class Auth
Inherits BaseUser
Public Property _ticket As Web.Security.FormsAuthenticationTicket
Public RememberMe As Boolean
Private _IssueDate As DateTime?
Public ReadOnly Property IssueDate As DateTime?
Get
Return _IssueDate
End Get
End Property
Private _Expired As Boolean
Public ReadOnly Property Expired As Boolean
Get
Return _Expired
End Get
End Property
Private _Expiration As DateTime?
Public ReadOnly Property Expiration As DateTime?
Get
Return _Expiration
End Get
End Property
Public Sub New(ticket As System.Web.Security.FormsAuthenticationTicket)
Me._ticket = ticket
Dim SignOutUser As Boolean = False
Try
If Not GetUserDetails() Then
SignOutUser = True
End If
Catch ex As Exception
SignOutUser = True
End Try
If SignOutUser Then
HttpContext.Current.Response.Redirect("~/", True)
SignOut()
End If
End Sub
Public ReadOnly Property IsAuthenticated() As Boolean
Get
Return HttpContext.Current.User.Identity.IsAuthenticated
End Get
End Property
Public Function SetAuthCookie() As Int16
Dim encTicket As String
Dim userData As String = CreateUserDataString()
If userData.Length > 0 And userData.Length < 4000 Then
Dim cookiex As HttpCookie = FormsAuthentication.GetAuthCookie(MyBase.Username, True)
Dim ticketx As FormsAuthenticationTicket = FormsAuthentication.Decrypt(cookiex.Value)
'Dim newTicket = New FormsAuthenticationTicket(ticket.Version, ticket.Name, ticket.IssueDate, ticket.Expiration, ticket.IsPersistent, userData, ticket.CookiePath)
'encTicket = FormsAuthentication.Encrypt(newTicket)
'Use existing cookie. Could create new one but would have to copy settings over...
'cookie.Value = encTicket
'cookie.Expires = newTicket.Expiration.AddHours(24)
'HttpContext.Current.Response.Cookies.Add(cookie)
Dim ticket As New FormsAuthenticationTicket(1, ticketx.Name, DateTime.Now, ticketx.Expiration, False, userData, ticketx.CookiePath)
encTicket = FormsAuthentication.Encrypt(ticket)
cookiex.Value = encTicket
'Dim cookie As New HttpCookie(FormsAuthentication.FormsCookieName, encTicket)
HttpContext.Current.Response.Cookies.Add(cookiex)
Else
Throw New ArgumentOutOfRangeException("User data length exceeds maximum", New ArgumentOutOfRangeException)
End If
Return encTicket.Length
End Function
Public Function GetUserDetails() As Boolean
Dim valid As Boolean = False
If _ticket IsNot Nothing Then
With _ticket
RememberMe = .IsPersistent
Username = .Name
_IssueDate = .IssueDate
_Expired = .Expired
_Expiration = .Expiration
Try
If .UserData.Length > 0 Then
valid = SetUserDataFromString(.UserData)
Else
'we have a problem
Return False
End If
Catch ex As Exception
'sign them out as they may have a cookie but the code may have changed so it errors thus make them login again.
'SignOut()
Throw ex
End Try
End With
End If
Return valid
End Function
Private Function CreateUserDataString() As String
Dim sData As New System.Text.StringBuilder
With sData
.Append(MyBase.UserID)
.Append("|") 'delimeter we are using
.Append(Int16.Parse(MyBase.UserType))
.Append("|")
.Append(Int16.Parse(MyBase.Security))
.Append("|") 'delimeter we are using
.Append(MyBase.FirstName)
.Append("|")
.Append(MyBase.LastName)
.Append("|")
.Append(MyBase.foo1)
.Append("|")
.Append(MyBase.foo2)
.Append("|")
.Append(MyBase.foo3)
.Append("|")
.Append(MyBase.foo4)
End With
Return sData.ToString
End Function
Public Function SetUserDataFromString(userData As String) As Boolean
Dim valid As Boolean = False
Dim sData As New System.Text.StringBuilder
'check we have a delimeter
Dim arUserData As String() = userData.Split("|")
Try
If arUserData.Count >= 9 Then '9 because that the user only stuff
With arUserData
MyBase.UserID = arUserData(0)
MyBase.UserType = arUserData(1)
MyBase.Security = arUserData(2)
MyBase.FirstName = arUserData(3)
MyBase.LastName = arUserData(4)
MyBase.foo1 = arUserData(5)
MyBase.foo2 = arUserData(6)
MyBase.foo3 = arUserData(7)
MyBase.foo4 = arUserData(8)
End With
valid = True
Else
valid = False
End If
Catch ex As Exception
Throw New ArgumentOutOfRangeException("User data length to short", New ArgumentOutOfRangeException)
End Try
Return valid
End Function
Public Sub SignOut()
FormsAuthentication.SignOut()
End Sub
As you have posted the code may be you would get some good answer. I would try to answer as per my understanding, hope that helps.
From where do you invoke CustomIdentity? In the first method in the Else part I think you might want to use Not IsAuthenticated if you want the user to redirect to the login page. Most of the times if the ticket is invalid you don't even have to do it the framework does it for you.
In the CustomIdentityUser you have a private member _Auth which you never use while returning some values through the properties. You are using Auth.UserName instead of _Auth.UserName, I am not sure how that works unless the UserName is static member.
Why is you custom Identity dependent on Auth? You can pass on the required data to the custom identity through contructor or by exposing public setters. Why do you need the auth ticket inside the identity?
The Auth Class has the expiration date and other stuff which is not required. You can have a simple User class to store the basic details of the user. Why are you redirecting the user from the constructor of Auth?
The GetUserDetails and SetUserDataFromString I do not know why do you need all these methods. Its just matter of Serializing and Deserializing the User class.
I understand that you must have referred some blog out there to implement this authentication, but you have lot of scope to simplify this.
Read this post. Specifically how the custom principal is implemented and how the authticket is set and the PostAuthenticateRequest method.
Here is some sample code that might help
interface ICustomPrincipal : IPrincipal
{
int UserId { get; set; }
string FirstName { get; set; }
string LastName { get; set; }
}
public class CustomPrincipal : ICustomPrincipal
{
public CustomPrincipal()
{
}
public CustomPrincipal(string userName)
{
Identity = new GenericIdentity(userName);
}
public int UserId
{
get;
set;
}
public string FirstName
{
get;
set;
}
public string LastName
{
get;
set;
}
public IIdentity Identity
{
get;
private set;
}
public bool IsInRole(string role)
{
return false;
}
}
public class User
{
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
public static class FormsAuthHelper
{
public static void SetAuthTicket(User user, HttpContextBase context)
{
var serializer = new JavaScriptSerializer();
var userData = serializer.Serialize(user);
var authTicket = new FormsAuthenticationTicket(
1, user.UserName,
DateTime.Now, DateTime.Now.AddMinutes(30),
false, userData);
var ticket = FormsAuthentication.Encrypt(authTicket);
var faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, ticket);
context.Response.Cookies.Add(faCookie);
}
public static void Logout()
{
FormsAuthentication.SignOut();
FormsAuthentication.RedirectToLoginPage();
}
public static CustomPrincipal GetPrincipal(User user)
{
return new CustomPrincipal(user.UserName) { FirstName = user.FirstName, LastName = user.LastName, UserId = user.EntityId };
}
}
Post authenticate request event looks like this
protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
var authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null || authCookie.Value == string.Empty)
return;
try
{
var ticket = FormsAuthentication.Decrypt(authCookie.Value);
var serializer = new JavaScriptSerializer();
var user = serializer.Deserialize<User>(ticket.UserData);
var newUser = FormsAuthHelper.GetPrincipal(user);
HttpContext.Current.User = newUser;
}
catch
{
//do nothing
}
}
And finally when the user logs in
public ActionResult Login(LoginModel loginModel)
{
if (ModelState.IsValid)
{
var user = _userRepository.Get(x => x.UserName == loginModel.UserName).SingleOrDefault();
if (user != null && PasswordHash.ValidatePassword(loginModel.Password, user.Password))
{
FormsAuthHelper.SetAuthTicket(user, HttpContext);
return RedirectToAction("Index", "Home");
}
ModelState.AddModelError("NotFound", "User not found");
}
return View(loginModel);
}

How do you mock ServiceStack ISession using Moq and StructureMap?

I'm using ServiceStack / StructureMap / Moq. The service makes a call to Session, which is type ServiceStack.CacheAccess.ISession. For unit tests, I created a Mock object using Moq, and added it to the StructureMap configuration:
protected Mock<ISession> sessionMock = new Mock<ISession>();
ObjectFactory.Configure(
cfg =>
{
cfg.For<ISession>().Use(sessionMock.Object);
However, I was not surprised when the Session object was null -- I'm pretty sure I'm leaving out a step. What else do I need to do to fill my Session property with a mock object?
[EDIT] Here's a simple test scenario
Code to test. Simple request / service
[Route("getKey/{key}")]
public class MyRequest:IReturn<string>
{
public string Key { get; set; }
}
public class MyService:Service
{
public string Get(MyRequest request)
{
return (string) Session[request.Key];
}
}
The base test class and MockSession classes
// test base class
public abstract class MyTestBase : TestBase
{
protected IRestClient Client { get; set; }
protected override void Configure(Container container)
{
// this code is never reached under any of my scenarios below
container.Adapter = new StructureMapContainerAdapter();
ObjectFactory.Initialize(
cfg =>
{
cfg.For<ISession>().Singleton().Use<MockSession>();
});
}
}
public class MockSession : ISession
{
private Dictionary<string, object> m_SessionStorage = new Dictionary<string, object>();
public void Set<T>(string key, T value)
{
m_SessionStorage[key] = value;
}
public T Get<T>(string key)
{
return (T)m_SessionStorage[key];
}
public object this[string key]
{
get { return m_SessionStorage[key]; }
set { m_SessionStorage[key] = value; }
}
}
And tests. See comments for where I'm seeing the failure. I didn't really expect versions 1 & 2 to work, but hoped version 3 would.
[TestFixture]
public class When_getting_a_session_value:MyTestBase
{
[Test]
public void Test_version_1()
{
var session = ObjectFactory.GetInstance<MockSession>();
session["key1"] = "Test";
var request = new MyRequest {Key = "key1"};
var client = new MyService(); // generally works fine, except for things like Session
var result = client.Get(request); // throws NRE inside MyService
result.ShouldEqual("Test");
}
[Test]
public void Test_version_2()
{
var session = ObjectFactory.GetInstance<MockSession>();
session["key1"] = "Test";
var request = new MyRequest {Key = "key1"};
var client = ObjectFactory.GetInstance<MyService>();
var result = client.Get(request); // throws NRE inside MyService
result.ShouldEqual("Test");
}
[Test]
public void Test_version_3()
{
var session = ObjectFactory.GetInstance<MockSession>();
session["key1"] = "Test";
var request = new MyRequest {Key = "key1"};
var client = CreateNewRestClient();
var result = client.Get(request); // throws NotImplementedException here
result.ShouldEqual("Test");
}
}
It looks like you're trying to create unit tests, but you're using an AppHost like you wound an Integration test. See this previous answer for differences between the two and docs on Testing.
You can mock the Session by registering an instance in Request.Items[Keywords.Session], e.g:
[Test]
public void Can_mock_IntegrationTest_Session_with_Request()
{
using var appHost = new BasicAppHost(typeof(MyService).Assembly).Init();
var req = new MockHttpRequest();
req.Items[Keywords.Session] = new AuthUserSession {
UserName = "Mocked"
};
using var service = HostContext.ResolveService<MyService>(req);
Assert.That(service.GetSession().UserName, Is.EqualTo("Mocked"));
}
Otherwise if you set AppHost.TestMode=true ServiceStack will return the IAuthSession that's registered in your IOC, e.g:
[Test]
public void Can_mock_UnitTest_Session_with_IOC()
{
using var appHost = new BasicAppHost
{
TestMode = true,
ConfigureContainer = container =>
{
container.Register<IAuthSession>(c => new AuthUserSession {
UserName = "Mocked",
});
}
}.Init();
var service = new MyService {
Request = new MockHttpRequest()
};
Assert.That(service.GetSession().UserName, Is.EqualTo("Mocked"));
}

Resources