I've got a GraphQL mutation using HotChocolate with the [Authorize] attribute from HotChocolate.AspNetCore.Authorization to enforce authorization on my GraphQL endpoints.
This works fine, I can only call the mutation once I'm logged in as an Admin ...
... but now I'd like to retrieve the user which is authorized, but I don't seem to find a way to do it.
[ExtendObjectType(Name = "Mutation")]
[Authorize(Roles = new[] { "Administrators" })]
public class MyMutations
{
public bool SomeMethod()
{
// In a regular Web API controller, you can do User.Identity.Name to fetch the user name of the current user. What is the equivalent in Hot Chocolate?
var userName = "";
return false;
}
}
Any ideas?
HotChocolate uses the asp.net core authentication mechanisms, so you can get the user using the HttpContext.
[ExtendObjectType(Name = "Mutation")]
[Authorize(Roles = new[] { "Administrators" })]
public class MyMutations
{
public bool SomeMethod([Service] IHttpContextAccessor contextAccessor)
{
var user = contextAccessor.HttpContext.User; // <-> There is your user
// In a regular Web API controller, you can do User.Identity.Name to fetch the user name of the current user. What is the equivalent in Hot Chocolate?
var userName = "";
return false;
}
}
Related
I have this code that is supposed to set claims for a user. It works fine when I use identity and the default login. However, when I use jwt as authentication in another application, I don't have ApplicationUser as my ApplicationUser is stored in the other application that authenticates the user. How can I customize this code so that it works with jwt?
private readonly SignInManager<TIdentityUser> _signInManager;
public CustomClaimsCookieSignInHelper(SignInManager<TIdentityUser> signInManager)
{
_signInManager = signInManager;
}
public async Task SignInUserAsync(TIdentityUser user, bool isPersistent, IEnumerable<Claim> customClaims)
{
var claimsPrincipal = await _signInManager.CreateUserPrincipalAsync(user);
var identity = claimsPrincipal.Identity as ClaimsIdentity;
var claims = (from c in claimsPrincipal.Claims select c).ToList();
var savedClaims = claims;
if (customClaims != null)
{
identity.AddClaims(customClaims);
}
await _signInManager.Context.SignInAsync(IdentityConstants.ApplicationScheme,
claimsPrincipal,
new AuthenticationProperties { IsPersistent = isPersistent });
}
I guess my main intention is to set my users claims in the httpcontext and not in a cookie and I want to do that without using identity.
EDIT:
My application structure
AuthenticationApp (server)
Responsible for authenticating users
Generates and Decodes Jwt
Checks if the user has the appropriate roles and returns true/false via rest api
MainApp (client)
Makes an api call to AuthenticationApp
Does not use identity at all
Sends Jwt everytime I need to check the role of the user
I understand that I will be able to decode the jwt client side. However, I do not know where I can store the decoded jwt details so that I can use it in the view. My initial idea was to use Httpcontext like normal applications that user Identity. However, I am stuck with the code above.
For sharing the Identity information between Controller and View, you could sign the User information by HttpContext.SignInAsync.
Try steps below to achieve your requirement:
Controller Action
public async Task<IActionResult> Index()
{
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme, ClaimTypes.Name, ClaimTypes.Role);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, "edward"));
identity.AddClaim(new Claim(ClaimTypes.Name, "edward zhou"));
//add your own claims from jwt token
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, new AuthenticationProperties { IsPersistent = true });
return View();
}
View
#foreach (var item in Context.User.Claims)
{
<p>#item.Value</p>
};
To make above code work, register Authentication in Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//your rest code
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//your rest code
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
All my SignalR clients connect using a JWT bearer token. I utilize the [Authorize] attribute in my SignalR Hub.
This token contains a userId which can be used to check if a user has read access on the resource through the resource's users property which contains a List<PuppyUserPermission> that look like this:
public class PuppyUserPermission
{
public string userId { get; set; }
public bool read { get; set; }
public bool write { get; set; }
}
The question is: how do I connect the dots here? Ideally, instead of something like
[Authorize]
public class PuppyHub : Hub
{
public async Task SendPuppy(Puppy pup)
{
await Clients.All.SendAsync(pup);
}
}
I would so something like the following (this is more pseudo code than anything else, as I don't use valid methods):
[Authorize]
public class PuppyHub : Hub
{
public async Task SendPuppy(Puppy pup)
{
var clients = Puppy.users.Where(u => u.read == true);
await clients.SendAsync(pup);
}
}
Basically, I'd like to ensure that the clients recieving the Puppy object via SignalR would be authorized users on the resource. Problem is, Clients is just a list of string client IDs, and I'm not sure how to go about tying them to actual users on my Puppy resource.
How do I go about achieving this?
From the beginning, I had the feeling that the answer lay in IUserIdProvider, but I didn't see how that would work for multiple users.
I finally found the answer, but it'll definitely need some cleanup.
First, create your own implementation of IUserIdProvider as follows:
public class MyUserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
var username = connection.User.Claims.Where(x => x.Type == "THE_CLAIM_YOU_WANT_TO_USE_TO_IDENTIFY_USERS").First().Value;
return username;
}
}
Next, register it using DI:
services.AddSingleton<IUserIdProvider, MyUserIdProvider >();
Now, when you want to send events from the server, use DI in your constructor to pull down an instance of your SignalR Hub as per usual:
private IHubContext<PuppyHub> puppyHub { get; }
public UsersController(IHubContext<PuppyHub> _puppyHub)
{
puppyHub = _puppyHub;
}
Then, where when you want to tell your clients about the new Puppy:
// ... typical controller code
// assume we have a var, puppy, with a list of authorized users
// use System.Linq to get a list of userIds where the user is authorized to read the puppy
var authorizedUsers = (IReadOnlyList<string>)puppy.users.Where(x => x.permissions.read == true).Select(i => i._id).ToList();
// send the new puppy to the authorized users
await puppyHub.Clients.Users(authorizedUsers).SendAsync("SendPuppy", puppy);
And viola! You have now done resource-based authorization with SignalR.
I've got a custom claim added to my ApplicationUser class as follows:
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
if(Theme != null)
userIdentity.AddClaim(new Claim("ThemeBundle", Theme.Bundle));
return userIdentity;
}
public int? ThemeId { get; set; }
[ForeignKey("ThemeId")]
public virtual Theme Theme { get; set; }
}
I extended Identity like this:
public static class IdentityExtensions
{
public static string GetThemeBundle(this IIdentity identity)
{
var claim = ((ClaimsIdentity) identity).FindFirst("ThemeBundle");
// Test for null to avoid issues during testing
return (claim != null) ? claim.Value : string.Empty;
}
}
I update the model behind the claim from the following Action Method:
public ActionResult ChangeTheme(int id)
{
var theme = _db.Themes.Find(id);
if (theme == null)
return HttpNotFound();
var userId = User.Identity.GetUserId();
var user = _db.Users.Find(userId);
user.Theme = theme;
_db.SaveChanges();
return RedirectToAction("Index", "Home");
}
I access it in a view (or elsewhere) like this:
User.Identity.GetThemeBundle()
When the user updates their "Theme" property with the "ChangeTheme" action, nothing happens until they log off and log back in.
I've spent all day mulling over more than the following QA's with no good result:
Update User Claim not Taking Effect. Why?
MVC 5 AddToRole requires logout before it works?
And thanks to #Brendan Green: ASP.NET Identity - Forcing a re-login with security stamp
So far the best I've got is that the page will refresh and the claim returns an empty string instead of the desired result, OR I redirect the user to the login screen. At least those are less ambiguous than nothing happening.
How on earth can I get the claim to update globally as soon as the user changes their "Theme" property? I'd settle for a good way to fully log the user off and back on if needed. Using the AuthenticationManager.Signout and .Signin methods doesn't quite do the trick.
As of Asp.Net MVC 6 and Asp.Identity 3.0.0-rc1-final you could use Task SignInManager<TUser>.RefreshSignInAsync(TUser user); in order to do that.
I have built a WebAPI for user login, the webAPI can generate Access Token, if the user provided correct UserName and password. My Question is how I can pass user role information to the MVC application also.
For example,
I have a MVC app controller below, how can I pass the role 'Admin, UserEditor' from the Web API? I know I can use another WebAPI call to check user role, but it is not a good idea to do it.
[Authorized("Admin,UserEditor")]
ActionResult EditUser(int? Id)
{
........
}
You can read role information from claims.
Step-1 Create Role-s
I created it seed, but your choice may be different.
public static class MyDbInitializer
{
public static void Seed(this ModelBuilder builder)
{
Guid adminRoleId = Guid.Parse("90a5d1bb-2cf0-4014-9f1a-2d9f644a2e22");
builder.Entity<IdentityRole<Guid>>().HasData(
new IdentityRole<Guid>
{
Id = adminRoleId,
Name = RoleIdentifier.admin,
NormalizedName = RoleIdentifier.admin.ToUpper(CultureInfo.GetCultureInfo("en-GB"))
});
}
}
Step-2 Claims
public static class RoleIdentifier
{
public const string admin = "admin";
public const string user = "user";
}
public static class JwtClaimIdentifier
{
public const string UserId = "user_id";
public const string UserName = "user_name";
public const string Role = "role";
}
Where you generate tokens, add the role name to the claims information.
...
... string role = await _userService.GetRole(userId);
... identity.FindFirst(JwtClaimIdentifier.Role)
Step-3 Add authorize att. to controllers.
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = RoleIdentifier.admin)]
public class FooController
{
}
When the logged in user wants to access this action, the possession of this role will match and access claims.
You need to use 2 authentication mechanisms (Bearer Tokens, and Cookies) because your are securing Web API end points using tokens and MVC 5 controllers using Cookies. I recommend you to check VS 2013 Web template with MVC core dependency selected. It contains all the code needed at your case. Inside the GrantResourceOwnerCredentials method you will find something similar to the below:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
Notice how there are oAuthIdentity for Web API, and cookiesIdentity for MVC application.
What steps must be done to implement basic authentication in ASP.NET MVC 5?
I have read that OWIN does not support cookieless authentication, so is basic authentication generally possible?
Do I need a custom attribute here? I am not sure about how these attributes work.
You can use this simple yet effective mechanism using a custom ActionFilter attribute:
public class BasicAuthenticationAttribute : ActionFilterAttribute
{
public string BasicRealm { get; set; }
protected string Username { get; set; }
protected string Password { get; set; }
public BasicAuthenticationAttribute(string username, string password)
{
this.Username = username;
this.Password = password;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var req = filterContext.HttpContext.Request;
var auth = req.Headers["Authorization"];
if (!String.IsNullOrEmpty(auth))
{
var cred = System.Text.ASCIIEncoding.ASCII.GetString(Convert.FromBase64String(auth.Substring(6))).Split(':');
var user = new { Name = cred[0], Pass = cred[1] };
if (user.Name == Username && user.Pass == Password) return;
}
filterContext.HttpContext.Response.AddHeader("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", BasicRealm ?? "Ryadel"));
/// thanks to eismanpat for this line: http://www.ryadel.com/en/http-basic-authentication-asp-net-mvc-using-custom-actionfilter/#comment-2507605761
filterContext.Result = new HttpUnauthorizedResult();
}
}
It can be used to put under Basic Authentication a whole controller:
[BasicAuthenticationAttribute("your-username", "your-password",
BasicRealm = "your-realm")]
public class HomeController : BaseController
{
...
}
or a specific ActionResult:
public class HomeController : BaseController
{
[BasicAuthenticationAttribute("your-username", "your-password",
BasicRealm = "your-realm")]
public ActionResult Index()
{
...
}
}
In case you need additional info check out this blog post that I wrote on the topic.
You can do this with a custom attribute. There is an implementation of a custom attribute that supports base authentication in the open source project SimpleSecurity, which you can download here. There is a reference application to demonstrate how it is used. It was originally developed to work with SimpleMembership in MVC 4 and has been recently ported to use ASP.NET Identity in MVC 5.
I wanted to amend the answer shared by Darkseal, because that code has a major security flaw. As written, that action filter does not actually terminate the request when res.End() is called. The user is prompted for credentials and a 401 response is returned if the credentials don't match, but the controller action is still executed on the server side. You need to set the filterContext.Result property to something in order for the request to terminate properly and not continue to the action method.
This was particularly bad for my situation, as I was trying to protect a web service endpoint that receives a data feed from a third party. As written, this action filter didn't protect anything because the data was still being pushed through my action method.
My "quick fix" is below:
public class BasicAuthenticationAttribute : ActionFilterAttribute
{
public string BasicRealm { get; set; }
protected string Username { get; set; }
protected string Password { get; set; }
public BasicAuthenticationAttribute(string username, string password)
{
this.Username = username;
this.Password = password;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var req = filterContext.HttpContext.Request;
var auth = req.Headers["Authorization"];
if (!String.IsNullOrEmpty(auth))
{
var cred = System.Text.ASCIIEncoding.ASCII.GetString(Convert.FromBase64String(auth.Substring(6))).Split(':');
var user = new { Name = cred[0], Pass = cred[1] };
if (user.Name == Username && user.Pass == Password) return;
}
var res = filterContext.HttpContext.Response;
res.AddHeader("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", BasicRealm ?? "Ryadel"));
filterContext.Result = new HttpUnauthorizedResult();
}
}
Great answer from #Darkseal. Here's the same code repurposed for use with ASP.NET Web API (close cousin to MVC). Same idea, slightly different namespaces and context classes. Add it to your classes and methods in exactly the same way.
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
public class BasicAuthenticationAttribute : ActionFilterAttribute
{
public string BasicRealm { get; set; }
protected string Username { get; set; }
protected string Password { get; set; }
public BasicAuthenticationAttribute(string username, string password)
{
Username = username;
Password = password;
}
public override void OnActionExecuting(HttpActionContext filterContext)
{
var req = filterContext.Request;
var auth = req.Headers.Authorization;
if (auth?.Scheme == "Basic")
{
var cred = Encoding.ASCII.GetString(Convert.FromBase64String(auth.Parameter)).Split(':');
var user = new { Name = cred[0], Pass = cred[1] };
if (user.Name == Username && user.Pass == Password) return;
}
filterContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
filterContext.Response.Headers.Add("WWW-Authenticate", string.Format("Basic realm=\"{0}\"", BasicRealm ?? "YourRealmName"));
}
}
HTTP basic authentication doesn't require a cookie. It's based on a HEADER in the HTTP request. The header is named Authorization and its value should be username and password combined into a string, "username:password" (all base64 encoded).
Sincerely I never used basic authentication with ASP.NET MVC, but I used Web API to create a custom attribute (you can start from here for WebAPI or here for MVC).
you can try this package on Nuget (AuthPackage)
its enables you to add authentication to your asp.net mvc easily.
install package using Package Manager Console:
Install-Package AuthPackage
add Connection String to your Web.config in (appSettings):
<add key="connectionString" value="connectionStringHere" />
you're ready to register users, login, logout
example:
public async Task<ActionResult> SignIn()
{
var context = System.Web.HttpContext.Current;
AuthUser authUser = new AuthUser(context);
await authUser.SignIn("waleedchayeb2#gmail.com", "123456");
return RedirectToAction("Index", "Home");
}
You can read the Documentation here
The Darkseal’s answer
[BasicAuthenticationAttribute("your-username", "your-password",
BasicRealm = "your-realm")]
has 2 disadvantages :
name and password are hardcoded and they support only single user.
More flexible solution should support multiple username/password pairs stored in configuration.
Microsoft describes a sample https://gm/aspnet/samples/tree/main/samples/aspnet/WebApi/BasicAuthentication.
public abstract class BasicAuthenticationAttribute : Attribute, IAuthenticationFilter
In overload of
abstract Task<IPrincipal> AuthenticateAsync(string userName, string password,
CancellationToken cancellationToken);
you can implement check to find if username/password from the header exist in configuration/secret list of username/password pairs
It’s also possible to create HTTP module that performs Basic Authentication. You can easily plug in an ASP.NET membership provider by replacing the CheckPassword method.
https://learn.microsoft.com/en-us/aspnet/web-api/overview/security/basic-authentication#basic-authentication-with-custom-membership
Example of OWIN implementation https://github.com/scottbrady91/Blog-Example-Classes/tree/master/OwinBasicAuthentication/WebApi
Possible implementation in .Net core is described in
https://github.com/mihirdilip/aspnetcore-authentication-basic
An application of ours "accidentally" used basic authentication because of the following code in Web.config:
<system.webServer>
<modules>
<remove name="FormsAuthentication" />
</modules>
... other stuff
</system.webServer>
The application is otherwise configured to use forms authentication.
The browser authentication window popped up whenever normal forms authentication would otherwise have been used.