Hello, I'm starting with private messages between ASP.net users, using SignalR, everything Works fine if I'm using ConnectionId - s, In this way I can take recipient's ConnectionId and send private message to this person. Now about problem, I want store messages in database and load them on login, I'm using Standard membership of ASP.net mvc5 application, so after reconnect ConnectionId is changing. I was reading article Mapping SignalR Users to Connections, I but cannot understand how to use IUserProvider Can you explain me how to make this taks. lot of thanks.
Here is my hub code:
[HubName("chatHub")]
public class ChatHub : Hub
{
static List<ApplicationUser> Users = new List<ApplicationUser>();
private ApplicationDbContext db = new ApplicationDbContext();
public void Send(string name, string message)
{
Clients.All.addMessage(name, message);
}
public void SendPM(string name, string privatemessage, string userid)
{
//This line not Works I've commented it but filling It's correct way
//Clients.User(userid).addPM(name, privatemessage);
Clients.Client(userid).addPM(name, privatemessage);
}
public void Connect(string userName)
{
var id = Context.ConnectionId;
var appuser = db.Users.FirstOrDefault(x => x.UserName == userName);
var dbUsers = db.Users.ToList();
if (!Users.Any(x => x.ConnectionId == id))
{
Users.Add(new ApplicationUser { ConnectionId = id, UserName = userName, Id = appuser.Id });
Clients.Caller.onConnected(id, userName, Users);
Clients.AllExcept(id).onNewUserConnected(id, userName);
}
}
public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled)
{
var item = Users.FirstOrDefault(x => x.ConnectionId == Context.ConnectionId);
if (item != null)
{
Users.Remove(item);
var id = Context.ConnectionId;
Clients.All.onUserDisconnected(id, item.UserName);
}
return base.OnDisconnected(stopCalled);
}
}
The article says:
By default, there will be an implementation that uses the user's IPrincipal.Identity.Name as the user name.
This means that for the mapping to work, you need to make user that users are logged in (authorized) when calling SignalR hub. The easiest way to ensure this is to add [Authorize] attribute to ChatHub and the controller that returns chat view.
If user is authorized, you can retrieve his username from current context by calling:
var username = Context.User.Identity.Name;
Then, the following should work (without the need to provide your own IUserIdProvider):
public void SendPM(string name, string privatemessage, username)
{
Clients.Client(username).addPM(name, privatemessage);
}
Related
I'm looking to create an ASP.NET 6 Core Identity solution where users can create groups.
The creator becomes admin of that group and can Crud other users to the group - (the other users may then become an admin of the group).
The creator also needs to exist in other groups with and without admin.
Is there an easy way to do this in Identity using roles as groups with policies/claims or are some new tables required? like this
https://www.codeproject.com/Tips/5276188/Implementing-User-Groups-using-Claims-in-ASP-NET-I?fid=1962554&df=90&mpp=25&sort=Position&view=Normal&spc=Relaxed&prof=True
Using claims by itself is enough for authorization (e.g. is this user an admin, can he promote this other user to admin status). But using claims limits you to string types, which could take arbitrary values. This puts the burden on you for making sure every value is as it's supposed to be.
To have a more sound DB schema, you should store memberships in a separate table. This means creating a many-to-many relation between User & Group to store membership, which would necessitate a lookup table, but seeing how you're using ASP.NET Core 5, and likely EF Core 5, it creates this table for you.
But likely you'd need to store when and by whom a user has been added to a group. This would mean storing some data on the lookup table. So if you need those, you should create an entity for that lookup table too.
As for the code, here's a starting point for you:
public record Group(string Name)
{
public const string AdminClaimName = "group_admin";
public Guid Id { get; init; } = Guid.Empty;
public List<IdentityUser> Members { get; set; } = new List<IdentityUser>();
}
[ApiController]
public class UserGroupController : ControllerBase
{
private readonly UserManager<IdentityUser> _userManager;
private readonly ApplicationDbContext _db;
public UserGroupController(UserManager<IdentityUser> userManager, ApplicationDbContext db)
{
_userManager = userManager;
_db = db;
}
public record GroupCreateRequest(string GroupName);
[HttpPost]
public async Task<IActionResult> CreateGroup(GroupCreateRequest request,
CancellationToken cancellationToken = default)
{
var user = await _userManager.GetUserAsync(User);
var group = new Group(request.GroupName);
await _db.Set<Group>().AddAsync(group, cancellationToken);
await _db.SaveChangesAsync(cancellationToken);
await _userManager.AddClaimAsync(user, new Claim(type: Group.AdminClaimName, value: group.Id.ToString()));
return Ok(group);
}
public record GroupMembershipRequest(string MemberUserId);
[HttpPost("{id:guid}/membership")]
public async Task<IActionResult> AddUserToGroup(Guid groupId,
GroupMembershipRequest request,
CancellationToken cancellationToken = default)
{
var user = await _userManager.GetUserAsync(User);
var claims = await _userManager.GetClaimsAsync(user);
// check if the current user is the admin of that group
if (claims.Any(c => c.Type == Group.AdminClaimName && c.Value == groupId.ToString()))
{
return Forbid();
}
var member = await _userManager.FindByIdAsync(request.MemberUserId);
var group = await _db.Set<Group>().FindAsync(groupId);
group.Members.Add(member);
await _db.SaveChangesAsync(cancellationToken);
return NoContent();
}
public record UserPromotionRequest(Guid GroupId, string MemberUserId);
[HttpPost("promote")]
public async Task<IActionResult> PromoteUserToAdmin(UserPromotionRequest request,
CancellationToken cancellationToken = default)
{
var user = await _userManager.GetUserAsync(User);
var claims = await _userManager.GetClaimsAsync(user);
// check if the current user is the admin of that group
if (claims.Any(c => c.Type == Group.AdminClaimName && c.Value == request.GroupId.ToString()))
{
return Forbid();
}
var member = await _userManager.FindByIdAsync(request.MemberUserId);
var group = await _db.Set<Group>().FindAsync(request.GroupId);
await _userManager.AddClaimAsync(member, new Claim(type: Group.AdminClaimName, value: group.Id.ToString()));
return NoContent();
}
}
(I've omitted some validations & null checks, you need to add those yourself.)
Notice the difference between User property of the controller (the user currently logged in) and user instance fetched from the database.
I am familiar with roles and authentication attributes in MVC, but as I am adding more and more information onto my database I think I ma going to run into a problem with primary keys being unencrypted or accessible.
I am using identity 2.1, so when a user is logged in I have access of their UserId and their CustomerID but my concern is that any user can go to /Customers/Delete/3 or any CustomerID and have access. Even if I created a GUID id or other encryption it could still be vulnerable to brute force attacks.
Is there a way in MVC to implement a check to only allow the current user to load pages that are related to them?
You can add extra field say "CreatedByUserId" to database table and when user access page check if CreatedByUserId matches with user id of logged in user or not.
You should be checking if the current logged in user has access to any of the information before you try and manipulate data. For example...
public async Task<HttpResponseMessage> DeleteCustomer(string customerId)
{
var appUser = await _authRepository.FindUser(User.Identity.GetUserName());
if(!_customerRepository.CanDeleteCustomer(appUser.Id, customerId){
return BadRequest();
}
// they have access so do what you need to do down here..
}
You can create a custom Authorize Attribute and a table in the database in which you store which user is allowed what Pages (Actions) or Controllers and then check that table while authorizing that whether the user is authorized for that Page/Controller. I have created an example for you in which I used Custom Authorize Attribute named MyAuthorizeAttribute and a database table named PageRoles.
Custom Authorize Attribute:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
readonly ApplicationDbContext _db = new ApplicationDbContext();
string _pageName;
public MyAuthorizeAttribute(string pageNameFromController)
{
_pageName = pageNameFromController;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var userId = httpContext.User.Identity.GetUserId();
var pageRoles = db.PageRoles.Where(m => m.UserId == userId);
foreach (var item in pageRoles)
{
if (item.PageName == _pageName && item.UserId == userId)
{
return base.AuthorizeCore(httpContext);
}
}
return false;
}
}
Model used:
public class PageRole
{
public int Id { get; set; }
public string UserId { get; set; }
public string PageName { get; set; }
public virtual ApplicationUser User { get; set; }
}
and then you will just have to use the attribute on your controllers just like you use Authorize attribute:
[MyAuthorize("Home")]
public class HomeController : Controller
{ }
In ASP.NET MVC Identity,the relations data for Users and Roles is saved in AspNetUserRoles table, this table has two field:UserId,RoleId, but i want to add other fields to this table, such as department field.
So if an user logins in different departments,he will have different roles.
Anyone knows how to do it? Thanks in advance!
I Would Suggest you investigate ASPNet User Claims. You can assign different claims to a user with the identity manager, and based on the claim type of the user you will allow him access or not. Create a custom Claims Attribute which will be placed on top of the various controller to authenticate the user. this must be implemented based on your needs. the custom attribute will then fire before the controller gets executed and if the uses is allowed he will pass. else return to error page of you choice.
Sample Attribute usage
[ClaimsAuthorize(ClaimsData.EditAddress)]
public ActionResult CitiesPartial()
Attribute Authentication
public class ClaimsAuthorizeAttribute : AuthorizeAttribute
{
private readonly string _claimType;
public ClaimsAuthorizeAttribute(string type)
{
_claimType = type;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
var user = (ClaimsPrincipal)HttpContext.Current.User;
if (user.HasClaim(_claimType, "True"))
{
base.OnAuthorization(filterContext);
}
else
{
HandleUnauthorizedRequest(filterContext, _claimType + " Not Allowed ");
}
}
protected void HandleUnauthorizedRequest(AuthorizationContext filterContext, string message)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "action", "ClaimNotAuthorized" },
{ "controller", "Home" },
{"errorMessage", message }
});
}
public static bool AuthorizedFor(string claimType)
{
var user = (ClaimsPrincipal)HttpContext.Current.User;
return user.HasClaim(claimType, "True");
}
}
hope this helps.
I'm a little confused with the session documentation, so let's say i'm already send the authentication data from the client side and retrieve the ss-id and ss-pid like this:
var client = new JsonServiceClient("http://somewhere/theAPI/");
var response = client.Post(new Auth() {UserName = "myuser", Password = "password123"});
var myCookie= client.CookieContainer.GetCookies(new Uri("http://somewhere/theAPI"));
how I can retrieve the AuthSession information just like the surname, email, etc from the servicestack? do i need it store somewhere else like in memcache server, and retrieve from that?
or I need to build my authentication in the client side? and just use the API to retrieve the data?
Assuming you've already created a custom AuthUserSession, for example:
/// <summary>
/// Create your own strong-typed Custom AuthUserSession where you can add additional AuthUserSession
/// fields required for your application. The base class is automatically populated with
/// User Data as and when they authenticate with your application.
/// </summary>
public class CustomUserSession : AuthUserSession {
public string CustomId { get; set; }
}
And you've registered your custom AuthUserSession when configuring the AuthFeature plugin, like so:
public override void Configure(Container container)
{
//Register all Authentication methods you want to enable for this web app.
Plugins.Add(new AuthFeature(
() => new CustomUserSession(), //Use your own typed Custom UserSession type
new IAuthProvider[] {
new CredentialsAuthProvider(), //HTML Form post of UserName/Password credentials
// and any other auth providers you need
}));
}
Then you can expose this data to the client in a service you create. SocialBotstrapApi provides access to the current session information on the server like this: Use it as a model to create a UserAuth service that returns the information for just the current user.
public abstract class AppServiceBase : Service {
private CustomUserSession userSession;
protected CustomUserSession UserSession {
get {
return base.SessionAs<CustomUserSession>();
}
}
}
[Route("/userauths")]
public class UserAuths
{
public int[] Ids { get; set; }
}
public class UserAuthsResponse
{
public UserAuthsResponse()
{
this.Users = new List<User>();
this.UserAuths = new List<UserAuth>();
this.OAuthProviders = new List<UserOAuthProvider>();
}
public CustomUserSession UserSession { get; set; }
public List<User> Users { get; set; }
public List<UserAuth> UserAuths { get; set; }
public List<UserOAuthProvider> OAuthProviders { get; set; }
}
//Implementation. Can be called via any endpoint or format, see: http://servicestack.net/ServiceStack.Hello/
public class UserAuthsService : AppServiceBase
{
public object Any(UserAuths request)
{
var response = new UserAuthsResponse {
UserSession = base.UserSession,
Users = Db.Select<User>(),
UserAuths = Db.Select<UserAuth>(),
OAuthProviders = Db.Select<UserOAuthProvider>(),
};
response.UserAuths.ForEach(x => x.PasswordHash = "[Redacted]");
response.OAuthProviders.ForEach(x =>
x.AccessToken = x.AccessTokenSecret = x.RequestTokenSecret = "[Redacted]");
if (response.UserSession != null)
response.UserSession.ProviderOAuthAccess.ForEach(x =>
x.AccessToken = x.AccessTokenSecret = x.RequestTokenSecret = "[Redacted]");
return response;
}
}
I am making an ASP.Net MVC3 application. I use for now the built in Authentication code that comes with a Visual Studio 2010 project. The problem is dat I need to retrieve the logged in user's database ID as soon as he has logged in. I do that now by adding code to the Login Action of the Account controller that retrieves the ID from the database by looking it up by username. This works for new logins, but not for "remembered" ones. On restarting the application the last user is automatically logged in again, but the Login code is not fired, so I do not get the database ID.
How can I solve this?
EDIT:
I tried to implement Daniel's solutions which looks promising and I came up with this code. It nevers gets called though! Where have I gone wrong?
Global.asax.cs:
protected void Application_Start()
{
Database.SetInitializer<StandInContext>(new StandInInitializer());
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
this.AuthenticateRequest +=
new EventHandler(MvcApplication_AuthenticateRequest);
}
void MvcApplication_AuthenticateRequest(object sender, EventArgs e)
{
if(Request.IsAuthenticated)
{
using (var db = new StandInContext())
{
var authenticatedUser = db.AuthenticatedUsers.SingleOrDefault(
user => user.Username == User.Identity.Name);
if (authenticatedUser == null)
return;
var person = db.Persons.Find(authenticatedUser.PersonID);
if (person == null)
return;
Context.User = new CustomPrincipal(
User.Identity, new string[] { "user" })
{
Fullname = person.FullName,
PersonID = person.PersonID,
};
}
}
}
You can use the AuthenticateRequest event in your Global.asax.cs:
protected void Application_AuthenticateRequest()
{
if (Request.IsAuthenticated)
{
// retrieve user from repository
var user = _membershipService.GetUserByName(User.Identity.Name);
// do other stuff
}
}
Update:
Now that I see what you're trying to do a little clearer, I would recommend against using sessions in this particular case. One reason is that Session requires a reference to System.Web, which you don't have access to from some places, like a business logic layer in a separate class library. IPrincipal, on the other hand, exists for this very reason.
If you need to store more user information than what IPrincioal provides, you simply implement it and add your own properties to it. Easier yet, you can just derive from GenericPrincipal, which implements IPrincipal and adds some basic role checking functionality:
CustomPrincipal.cs
public class CustomPrincipal : GenericPrincipal
{
public CustomPrincipal(IIdentity identity, string[] roles)
: base(identity, roles) { }
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
...
}
So then you replace the default principal with your own in AuthenticateRequest, as before:
Global.asax.cs
protected void Application_AuthenticateRequest()
{
if (Request.IsAuthenticated)
Context.User = _securityService.GetCustomPrincipal(User.Identity.Name);
}
And that is it. The greatest advantage you get is that you automatically get access to your user data from literally everywhere, without having to stick a userId parameter into all your methods. All you need to do is cast the current principal back to CustomPrincipal, and access your data like so:
From your razor views:
<p>Hello, #((CustomPrincipal)User).FirstName!</p>
From your controllers:
var firstName = ((CustomPrincipal)User).FirstName;
From a business logic layer in another assembly:
var firstName = ((CustomPrincipal)Thread.CurrentPrincipal).FirstName;
To keep things DRY, you could pack this into an extension method and hang it off IPrincipal, like so:
public static class PrincipalExtensions
{
public static string GetFirstName(this IPrincipal principal)
{
var customPrincipal = principal as CustomPrincipal;
return customPrincipal != null ? customPrincipal.FirstName : "";
}
}
And then you would just do #User.GetFirstName(), var userName = User.GetFirstName(), Thread.CurrentPrincipal.GetFirstName(), etc.
Hope this helps.
I wasn´t thinking clear. I was trying to store the userinfo in the Session object, while it available through the User object. Sorry to have wasted your time.