Offline authentication best practices in Xamarin - c#

I intend to create a Xamarin(.Forms) application, and it has to have authentication: the user should provide a username and password to obtain data.
The problem is that it has to work offline as well (meaning it shouldn't rely on a server for the authentication process).
I considered the following so far:
Storing username and password in Preferences - it is not encrypted, easy to reset, easy to obtain etc.
Storing username and password in database - it could be encrypted (I presume), easy to reset
What are the best practices? Is there any built-in solution for Xamarin?

It depends on your app needs - if you just need to authenticate the user to access some restricted data offline - I suggest you to hash the user name and password, and storing it in the local device (doesn't matter where, it's hashed).
If you need to use the username & password later on for authenticate it with the server, then you should use a secured data like keychain, Xamarin has a built in abstraction for using it, it's called Xamarin.Auth, where you can store your credentials securely.
*note - in jailbroken/rooted devices storing sensitive data is risky, use it with caution.
Edit - I have added a working code sample of how to consume and use these services:
1) In your portable project add this interface:
public interface ISecuredDataProvider
{
void Store(string userId, string providerName, IDictionary<string, string> data);
void Clear(string providerName);
Dictionary<string, string> Retreive(string providerName);
}
2) In your Android project, add this implementation:
public class AndroidSecuredDataProvider : ISecuredDataProvider
{
public void Store(string userId, string providerName, IDictionary<string, string> data)
{
Clear(providerName);
var accountStore = AccountStore.Create(Android.App.Application.Context);
var account = new Account(userId, data);
accountStore.Save(account, providerName);
}
public void Clear(string providerName)
{
var accountStore = AccountStore.Create(Android.App.Application.Context);
var accounts = accountStore.FindAccountsForService(providerName);
foreach (var account in accounts)
{
accountStore.Delete(account, providerName);
}
}
public Dictionary<string, string> Retreive(string providerName)
{
var accountStore = AccountStore.Create(Android.App.Application.Context);
var accounts = accountStore.FindAccountsForService(providerName).FirstOrDefault();
return (accounts != null) ? accounts.Properties : new Dictionary<string, string>();
}
}
3) In your iOS project, add this implementation:
public class IOSSecuredDataProvider : ISecuredDataProvider
{
public void Store(string userId, string providerName, IDictionary<string, string> data)
{
Clear(providerName);
var accountStore = AccountStore.Create();
var account = new Account(userId, data);
accountStore.Save(account, providerName);
}
public void Clear(string providerName)
{
var accountStore = AccountStore.Create();
var accounts = accountStore.FindAccountsForService(providerName);
foreach (var account in accounts)
{
accountStore.Delete(account, providerName);
}
}
public Dictionary<string, string> Retreive(string providerName)
{
var accountStore = AccountStore.Create();
var accounts = accountStore.FindAccountsForService(providerName).FirstOrDefault();
return (accounts != null) ? accounts.Properties : new Dictionary<string, string>();
}
}
4) An example of Usage - after fetching facebook token, we'll store it like this:
securedDataProvider.Store(user.Id, "FacebookAuth", new Dictionary<string, string> { { "FacebookToken", token } });
Note - I added two layers of data in order to have the first layer to indicate whether the authentication was Facebook/UserName-Password/other.
The 2nd layer was the data itself - i.e facebook token / credentials.

Related

Forcing user to sign in with their Google Organization (G Suite) account in mvc c#

I have implemented google authentication in my mvc site. Here is my sample code-
AuthConfig.cs
public static class AuthConfig
{
private static string GoogleClientId = ConfigurationManager.AppSettings["GoogleClientId"];
private static string GoogleClientSecret = ConfigurationManager.AppSettings["GoogleClientSecret"];
public static void RegisterAuth()
{
GoogleOAuth2Client clientGoog = new GoogleOAuth2Client(GoogleClientId, GoogleClientSecret);
IDictionary<string, string> extraData = new Dictionary<string, string>();
OpenAuth.AuthenticationClients.Add("google", () => clientGoog, extraData);
}
}
Global.asax
AuthConfig.RegisterAuth();
AccountController.cs
public ActionResult RedirectToGoogle()
{
string provider = "google";
string returnUrl = "";
return new ExternalLoginResult(provider, Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
}
[AllowAnonymous]
public ActionResult ExternalLoginCallback(string returnUrl)
{
string ProviderName = OpenAuth.GetProviderNameFromCurrentRequest();
if (ProviderName == null || ProviderName == "")
{
NameValueCollection nvs = Request.QueryString;
if (nvs.Count > 0)
{
if (nvs["state"] != null)
{
NameValueCollection provideritem = HttpUtility.ParseQueryString(nvs["state"]);
if (provideritem["__provider__"] != null)
{
ProviderName = provideritem["__provider__"];
}
}
}
}
GoogleOAuth2Client.RewriteRequest();
var redirectUrl = Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl });
var retUrl = returnUrl;
var authResult = OpenAuth.VerifyAuthentication(redirectUrl);
string ProviderDisplayName = OpenAuth.GetProviderDisplayName(ProviderName);
if (authResult.IsSuccessful)
{
string ProviderUserId = authResult.ProviderUserId;
}
return Redirect(Url.Action("Index", "User"));
}
This code is working fine. But I want to restrict the user to sign-in with his/her organizational account like "abc#example.com". Where I can specify the hosted domain property? When I created app id and secret for this app from google dev console, I saw Verify domain tab. Do I need to add my organizational domain here?
You can sort of. You can specify the hd (Hosted Domain) parameter within the Authentication URI parameters.
hd - OPTIONAL - The hd (hosted domain) parameter streamlines the login process for G Suite hosted accounts. By including the domain of the G Suite user (for example, mycollege.edu), you can indicate that the account selection UI should be optimized for accounts at that domain. To optimize for G Suite accounts generally instead of just one domain, use an asterisk: hd=*.
Don't rely on this UI optimization to control who can access your app, as client-side requests can be modified. Be sure to validate that the returned ID token has an hd claim value that matches what you expect (e.g. mycolledge.edu). Unlike the request parameter, the ID token claim is contained within a security token from Google, so the value can be trusted.

How to add a database retrieved value custom claim to JWT Token using ServiceStack

In AppHost.Configure I have the following code:
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new JwtAuthProvider
{
HashAlgorithm = "HS256",
RequireSecureConnection = requireSecureConnection,
AuthKeyBase64 = _configuration["AuthSettings:JwtAuthKeyBase64"],//Settings.Value.JwtAuthKeyBase64,
ExpireTokensIn = TimeSpan.FromHours(_configuration["AuthSettings:ExpireTokensIn"].ToDouble()), // JWT Token Expiry
ExpireRefreshTokensIn = TimeSpan.FromHours(_configuration["AuthSettings:ExpireRefreshTokensIn"].ToDouble()), // Refresh Token Expiry,
CreatePayloadFilter = (payload,session) => {
payload["ZipCode"] = "value_from_database_for_user";
}
},
new CustomCredentialsAuthProvider(), //HTML Form post of User/Pass
}));
The above code is standard ServiceStack JwtAuthProvider code.
You can see in the above code that the implementation for the anonymous function bound to CreatePayloadFilter would like to retrieve a value from the database, the user's ZipCode is the value and add that as a custom claim to the token.
For many obvious reasons, implementing the retrieval of the user's ZipCode in the AppHost is not easy, elegant or system/architecturally sound. Also it is not even possible, as I will not have the UserId, AppHost is just startup configuration code ran when the service starts.
You can also see in the above code that I have implemented a CustomCredentialsAuthProvider, I can load session data for the logged in user in CustomCredentialsAuthProvider and ServiceStack will map the session values to hydrate the appropriate JWT claims but I cannot add a custom claim via the ServiceStack session object, here is the implementation of CustomCredentialsAuthProvider:
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService,
string userName, string password)
{
return true;
//throw new NotImplementedException();
}
public override IHttpResult OnAuthenticated(IServiceBase authService,
IAuthSession session, IAuthTokens tokens,
Dictionary<string, string> authInfo)
{
//Fill IAuthSession with data you want to retrieve in the app eg:
session.FirstName = "some_firstname_from_db";
session.LastName = "some_lastname_from_db";
session.Roles = new List<string> {"role1", "role2"};
session.Permissions = new List<string> { "permission1", "permission2" };
session.Email = "test#test.com";
session.AuthProvider = "credentials";
//...
//Call base method to Save Session and fire Auth/Session callbacks:
return base.OnAuthenticated(authService, session, tokens, authInfo);
//Alternatively avoid built-in behavior and explicitly save session with
//authService.SaveSession(session, SessionExpiry);
//return null;
}
}
How can I add a custom claim to the ServiceStack framework created JWT, the value of that claim coming from the database and this implementation of this code not be function binding in AppHost.cs where I do not even have the UserId to retrieve the value anyways?
It sounds like you want to use a Custom UserSession to hold the additional metadata, you can tell ServiceStack to use your Custom Session when you register the AuthFeature, e.g:
Plugins.Add(new AuthFeature(() => new CustomUserSession(), ...)
After which you can cast the UserSession to your CustomUserSession to access the additional properties.
To add additional metadata to the JWT Token you'd use CreatePayloadFilter to add the data to the JWT Token and a corresponding PopulateSessionFilter to populate your Custom UserSession with the additional data.
I wanted to provide my final code for this even though #mythz provided the answer:
The CustomerUserSession:
[DataContract]
public class CustomUserSession: AuthUserSession
{
[DataMember]
public string ZipCode { get; set; }
}
The CustomCredentialsAuthProvider because I have a legacy db with all account info:
public override bool TryAuthenticate(IServiceBase authService,
string userName, string password)
{
return true;
}
public override IHttpResult OnAuthenticated(IServiceBase authService,
IAuthSession session, IAuthTokens tokens,
Dictionary<string, string> authInfo)
{
var customUserSession = (CustomUserSession) session;
//Fill IAuthSession with data you want to retrieve in the app eg:
customUserSession.FirstName = "some_firstname_from_db";
customUserSession.LastName = "some_lastname_from_db";
customUserSession.Roles = new List<string> {"role1", "role2"};
customUserSession.Permissions = new List<string> { "permission1", "permission2" };
customUserSession.Email = "test#test.com";
customUserSession.AuthProvider = "credentials";
customUserSession.CreatedAt = DateTime.UtcNow;
customUserSession.ZipCode = "92123";
//Call base method to Save Session and fire Auth/Session callbacks:
return base.OnAuthenticated(authService, session, tokens, authInfo);
}
And my AppHost adding of the ServiceStack AuthFeature:
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new JwtAuthProvider
{
HashAlgorithm = "HS256",
RequireSecureConnection = requireSecureConnection,
AuthKeyBase64 = _configuration["AuthSettings:JwtAuthKeyBase64"],//Settings.Value.JwtAuthKeyBase64,
ExpireTokensIn = TimeSpan.FromHours(_configuration["AuthSettings:ExpireTokensIn"].ToDouble()), // JWT Token Expiry
ExpireRefreshTokensIn = TimeSpan.FromHours(_configuration["AuthSettings:ExpireRefreshTokensIn"].ToDouble()), // Refresh Token Expiry,
CreatePayloadFilter = (payload,session) => {
payload["zipCode"] = ((CustomUserSession)session).ZipCode;
},
PopulateSessionFilter = (session, token, req) => {
((CustomUserSession) session).ZipCode = token["zipCode"];
}
},
new CustomCredentialsAuthProvider() //HTML Form post of User/Pass
}));

Value can not be null on Role Provider

I am beginner at MVC C#. Now I am trying to build a custom authentication with role provider, but when it check for user role its getting an error that "Value can not be null".
Here is my RoleProvider:
public class MyRoleProvider:RoleProvider
{
private int _cacheTimeoutInMinute = 20;
LoginGateway gateway=new LoginGateway();
public override string[] GetRolesForUser(string username)
{
if (!HttpContext.Current.User.Identity.IsAuthenticated)
{
return null;
}
//check cache
var cacheKey = string.Format("{0}_role", username);
if (HttpRuntime.Cache[cacheKey] != null)
{
return (string[])HttpRuntime.Cache[cacheKey];
}
string[] roles
= gateway.GetUserRole(username).ToArray();
if (roles.Any())
{
HttpRuntime.Cache.Insert(cacheKey, roles, null, DateTime.Now.AddMinutes(_cacheTimeoutInMinute), Cache.NoSlidingExpiration);
}
return roles;
}
public override bool IsUserInRole(string username, string roleName)
{
var userRoles = GetRolesForUser(username);
return userRoles.Contains(roleName);
}
Its always Getting error on reurn userRoles.Contains(roleName); (value can not be null(userName)) line. I used debug pointer, it shows gateway never been invoked.I,e: string[] roles = gateway.GetUserRole(username).ToArray(); so roles always remain null.
Although I am not sure that my GetUserRole method on gateway is correct or not: here is my gateway:
public string[] GetUserRole(string userName)
{
string role = null;
SqlConnection connection = new SqlConnection(connectionString);
string query = "select r.RoleType from RoleTable r join UserRoleTable ur on ur.RoleId=r.Id join UserTable u on u.UserId=ur.UserId where u.UserName='"+userName+"'";
SqlCommand command = new SqlCommand(query, connection);
connection.Open();
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
role = reader["RoleType"].ToString();
}
string[] roles = {role};
reader.Close();
connection.Close();
return roles;
}
Is there any problem with my code? and how could I solve this error?
it works if I delete or comment out httpContext and all code for
cache...Is there Anything wrong with cache portion code?? #Win
I won't worry about hitting database, unless you are developing Enterprise Application in which speed is an issue and every query count. Original ASP.Net Membership Provider doesn't use Cache at all.
Besides, ASP.Net Membership Provider is an very old technology - more than a decade old. If you are implementing for Enterprise Application, you might want to consider using Claim-Based Authentication.
Here is the cache service if you want to use -
using System;
using System.Collections.Generic;
using System.Runtime.Caching;
public class CacheService
{
protected ObjectCache Cache => MemoryCache.Default;
public T Get<T>(string key)
{
return (T) Cache[key];
}
public void Set(string key, object data, int cacheTime)
{
if (data == null)
return;
var policy = new CacheItemPolicy();
policy.AbsoluteExpiration = DateTime.Now + TimeSpan.FromMinutes(cacheTime);
Cache.Add(new CacheItem(key, data), policy);
}
public void Remove(string key)
{
Cache.Remove(key);
}
}

How can I authenticate against Active Directory in Nancy?

It's an outdated article, but http://msdn.microsoft.com/en-us/library/ff650308.aspx#paght000026_step3 illustrates what I want to do. I've chosen Nancy as my web framework because of it's simplicity and low-ceremony approach. So, I need a way to authenticate against Active Directory using Nancy.
In ASP.NET, it looks like you can just switch between a db-based membership provider and Active Directory just by some settings in your web.config file. I don't need that specifically, but the ability to switch between dev and production would be amazing.
How can this be done?
Really the solution is much simpler than it may seem. Just think of Active Directory as a repository for your users (just like a database). All you need to do is query AD to verify that the username and password entered are valid. SO, just use Nancy's Forms Validation and handle the connetion to AD in your implementation of IUserMapper. Here's what I came up with for my user mapper:
public class ActiveDirectoryUserMapper : IUserMapper, IUserLoginManager
{
static readonly Dictionary<Guid, long> LoggedInUserIds = new Dictionary<Guid, long>();
readonly IAdminUserValidator _adminUserValidator;
readonly IAdminUserFetcher _adminUserFetcher;
readonly ISessionContainer _sessionContainer;
public ActiveDirectoryUserMapper(IAdminUserValidator adminUserValidator, IAdminUserFetcher adminUserFetcher, ISessionContainer sessionContainer)
{
_adminUserValidator = adminUserValidator;
_adminUserFetcher = adminUserFetcher;
_sessionContainer = sessionContainer;
}
public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context)
{
_sessionContainer.OpenSession();
var adminUserId = LoggedInUserIds.First(x => x.Key == identifier).Value;
var adminUser = _adminUserFetcher.GetAdminUser(adminUserId);
return new ApiUserIdentity(adminUser);
}
public Guid Login(string username, string clearTextPassword, string domain)
{
var adminUser = _adminUserValidator.ValidateAndReturnAdminUser(username, clearTextPassword, domain);
var identifier = Guid.NewGuid();
LoggedInUserIds.Add(identifier, adminUser.Id);
return identifier;
}
}
I'm keeping a record in my database to handle roles, so this class handles verifying with AD and fetching the user from the database:
public class AdminUserValidator : IAdminUserValidator
{
readonly IActiveDirectoryUserValidator _activeDirectoryUserValidator;
readonly IAdminUserFetcher _adminUserFetcher;
public AdminUserValidator(IAdminUserFetcher adminUserFetcher,
IActiveDirectoryUserValidator activeDirectoryUserValidator)
{
_adminUserFetcher = adminUserFetcher;
_activeDirectoryUserValidator = activeDirectoryUserValidator;
}
#region IAdminUserValidator Members
public AdminUser ValidateAndReturnAdminUser(string username, string clearTextPassword, string domain)
{
_activeDirectoryUserValidator.Validate(username, clearTextPassword, domain);
return _adminUserFetcher.GetAdminUser(1);
}
#endregion
}
And this class actually verifies that the username/password combination exist in Active Directory:
public class ActiveDirectoryUserValidator : IActiveDirectoryUserValidator
{
public void Validate(string username, string clearTextPassword, string domain)
{
using (var principalContext = new PrincipalContext(ContextType.Domain, domain))
{
// validate the credentials
bool isValid = principalContext.ValidateCredentials(username, clearTextPassword);
if (!isValid)
throw new Exception("Invalid username or password.");
}
}
}

WCF UserNamePasswordValidator Caching

I have looked across the internet with no luck, I am trying to find a suitable way to cache a username and password token on the service side so each time a connection to the service is made I don't have to create a database connection.
This is what I am trying to achieve:
public class ServiceAuth : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
var user = Repository.Authenticate(userName, password);
if (user != null)
{
// Perform some secure caching
}
else
throw new FaultException("Login Failed");
}
}
Is it possible to use caching when validating credentials in C# 4.0 WCF using UserNamePasswordValidator?
If so, can someone give me some clues on how to achieve this?
I would like to request the super users not to delete the answer as that could help others who wants to find the solution for their issues..!
I have implemented the the following CUSTOM security manager using key-value pair Dictionary collection for caching. Hope this helps
public class SecurityManager : UserNamePasswordValidator
{
//cacheCredentials stores username and password
static Dictionary<string, string> cacheCredentials = new Dictionary<string, string>();
//cacheTimes stores username and time that username added to dictionary.
static Dictionary<string, DateTime> cacheTimes = new Dictionary<string, DateTime>();
public override void Validate(string userName, string password)
{
if (userName == null || password == null)
{
throw new ArgumentNullException();
}
if (cacheCredentials.ContainsKey(userName))
{
if ((cacheCredentials[userName] == password) && ((DateTime.Now - cacheTimes[userName]) < TimeSpan.FromSeconds(30)))// && timespan < 30 sec - TODO
return;
else
cacheCredentials.Clear();
}
if (Membership.ValidateUser(userName, password))
{
//cache usename(key) and password(value)
cacheCredentials.Add(userName, password);
//cache username(key), time that username added to dictionary
cacheTimes.Add(userName, DateTime.Now);
return;
}
throw new FaultException("Authentication failed for the user");
}
}

Categories