OWIN WebApi Entity Framework with OAuth Identity - asp.net

I'm experimenting with self hosted OWIN for a WebApi/Entity Framework project
I've created the Startup Class and configured both OWIN and WebApi using UseOAuthBearerAuthentication and UseOAuthAuthorizationServer with Provider defined to a Class deriving from OAuthAuthorizationServerProvider
Provider = new ApplicationOAuthServerProvider() // :OAuthAuthorizationServerProvider
this Class overrides
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{}
validate the user creates a ClaimsIdentity returning a token encoding the associated claims in my case NameIdentifier, Name and Role (Role is "Admin")
Everything works as expected and token is returned.
Now I'd like to take advantage of the associated claims from inside an ApiController.
Problem is User.Identityobject has only AuthentiationType isAuthenticated and Name properties all associated Claims are not there and I can't do much with Name property.
I see that by using
[Authorize (Roles="Admin")]
I'm able to access the ApiController so the Role Claim is available somewhere but the other claims I'm not able to access;
is there a way to solve my issue???
[Authorize (Roles="Admin")]
public class TestController : ApiController
{
public async Task<Account> Get()
{
var principal = User.Identity;
.... find and return data for user ID
}
}
Here are the Classes I've used
public class Startup
{
// This method is required.
public void Configuration(IAppBuilder app)
{
// Use cors on server level
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
// Configure OWIN to authenticate incoming requests.
ConfigureAuth(app);
// Use the extension method provided by the WebApi.Owin library.
app.UseWebApi(ConfigureWebApi());
}
private void ConfigureAuth(IAppBuilder app)
{
// Make sure a single instance of an EF context is created per OwinContext.
app.CreatePerOwinContext<ApplicationDbContext>(ApplicationDbContext.Create);
var OAuthOptions = new OAuthAuthorizationServerOptions{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthServerProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
// Debug Only
AllowInsecureHttp = true
};
// The server is added to the options object, which specifies other configuration items,
// and which is then passed into the middleware pipeline.
app.UseOAuthAuthorizationServer(OAuthOptions);
// Indicate that we want to return Bearer Tokens
// passing the default implementation for OAuthBearerAuthenticationOptions,
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
private HttpConfiguration ConfigureWebApi()
{
var config = new HttpConfiguration();
//Add JSON formetters
// Configure api routes
config.Routes.MapHttpRoute(
"DefaultApi",
"api/{controller}/{id}",
new { id = RouteParameter.Optional });
return config;
}
}
ApplicationOAuthServerProvider Class
public class ApplicationOAuthServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// This call is required...
await Task.FromResult(context.Validated());
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
if (context.Password == "Password")
{
// Create or retrieve a ClaimsIdentity to represent the
// ClaimsIdentity is created to represent the user data, including any Claims the user should have.
ClaimsIdentity identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, "120"));
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
identity.AddClaim(new Claim(ClaimTypes.Role, "Admin"));
// ClaimsIdentity is be encoded into an Access Token
context.Validated(identity);
}
else
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
context.Rejected();
}
}
}

Related

ASP.NET Core 2.1 Jwt setting custom claims

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?}");
});
}
}

Use WFC service calls as UserStore for ASP.NET Identity

I am creating a web forms application that uses a WCF service to interact with the database and other applications. This web forms application has no access to the database.
I would like to use ASP.Net Identity for user management. I have already created a custom UserStore and RoleStore by following this tutorial, Overview of Custom Storage Providers for ASP.NET Identity, as shown below.
public class UserStore : IUserStore<IdentityUser, long>, IUserRoleStore<IdentityUser, long>
{
UserServiceClient userServiceClient = new UserServiceClient();
public Task CreateAsync(IdentityUser user)
{
string userName = HttpContext.Current.User.Identity.GetUserName();
Genders gender = (Genders)user.CoreUser.Gender.GenderId;
UserDto userDto = userServiceClient.CreateUser(user.CoreUser.FirstName, user.CoreUser.LastName, gender, user.CoreUser.EmailAddress, user.CoreUser.Username, userName, user.CoreUser.Msisdn);
return Task.FromResult<UserDto>(userDto);
}
public Task DeleteAsync(IdentityUser user)
{
bool success = userServiceClient.DeactivateUser(user.CoreUser.UserId, "");
return Task.FromResult<bool>(success);
}
public Task<IdentityUser> FindByIdAsync(long userId)
{
UserDto userDto = userServiceClient.GetUserByUserId(userId);
return Task.FromResult<IdentityUser>(new IdentityUser { CoreUser = userDto, UserName = userDto.Username });
}
public Task<IdentityUser> FindByNameAsync(string userName)
{
UserDto userDto = userServiceClient.GetUserByUsername(userName);
return Task.FromResult<IdentityUser>(new IdentityUser { CoreUser = userDto, UserName = userDto.Username });
}
public Task UpdateAsync(IdentityUser user)
{
Genders gender = (Genders)user.CoreUser.Gender.GenderId;
UserDto userDto = userServiceClient.UpdateUserDetails(user.CoreUser.UserId, user.CoreUser.FirstName, user.CoreUser.LastName, gender, user.CoreUser.EmailAddress, user.CoreUser.Msisdn, "");
return Task.FromResult<UserDto>(userDto);
}
public void Dispose()
{
throw new NotImplementedException();
}
public Task AddToRoleAsync(IdentityUser user, string roleName)
{
throw new NotImplementedException();
}
public Task<IList<string>> GetRolesAsync(IdentityUser user)
{
List<UserRoleDto> roles = userServiceClient.GetUserRoles(user.Id);
return Task.FromResult<IList<string>>(roles.Select(r => r.Role.RoleName).ToList());
}
public Task<bool> IsInRoleAsync(IdentityUser user, string roleName)
{
throw new NotImplementedException();
}
public Task RemoveFromRoleAsync(IdentityUser user, string roleName)
{
throw new NotImplementedException();
}
}
That is the UserStore. Now the issue is implementing this for Identity.
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
In the class above that comes predefined with the template, there's the line:
app.CreatePerOwinContext(ApplicationDbContext.Create);
Now I don not have an ApplicationDbContext since this is handled in the WCF. Also, in the IdentityConfig class in the App_Start folder, there's the method Create that has this line,
var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
Again, i have no idea with what to replace the ApplicationDbContext. Am I doing this right? Is the tutorial I followed sufficient to help me with what I need?
I used this link, ASP.NET Identity 2.0 Extending Identity Models and Using Integer Keys Instead of Strings
The issue was more about the fact that my user id was an long instead of the default string. I also did not need to pass the context as my UserStore did not expect a context in it's constructor

ASP.NET WebAPI Identity 2 - Error with GetOwinContext on user registration /Register method

I have a WebAPI 2.1 application and I am having a problem with User Registration. I placed a breakpoint on the first line of the Register method but it is not reached. Instead it fails in the area below:
public ApplicationUserManager UserManager
{
get
{
var a = Request; // this is null !!
return _userManager ?? Request.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
[AllowAnonymous]
[Route("Register")]
[ValidateModel]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
var user = new ApplicationUser() { // <<<<< Debug breakpoint here never reached
Email = model.Email,
FirstName = model.FirstName,
LastName = model.LastName,
OrganizationId = 1,
OrganizationIds = "1",
RoleId = (int)ERole.Student,
SubjectId = 1,
SubjectIds = "1",
UserName = model.UserName
};
System.ArgumentNullException was unhandled by user code
HResult=-2147467261
Message=Value cannot be null.
Parameter name: request
Source=System.Web.Http.Owin
ParamName=request
StackTrace:
at System.Net.Http.OwinHttpRequestMessageExtensions.GetOwinContext(HttpRequestMessage request)
at WebRole.Controllers.AccountController.get_UserManager() in c:\G\abr\WebRole\Controllers\Web API - Data\AccountController.cs:line 50
at WebRole.Controllers.AccountController.Dispose(Boolean disposing) in c:\G\ab\WebRole\Controllers\Web API - Data\AccountController.cs:line 376
at System.Web.Http.ApiController.Dispose()
at System.Web.Http.Cors.AttributeBasedPolicyProviderFactory.SelectAction(HttpRequestMessage request, IHttpRouteData routeData, HttpConfiguration config)
at System.Web.Http.Cors.AttributeBasedPolicyProviderFactory.GetCorsPolicyProvider(HttpRequestMessage request)
InnerException:
If anyone could give me any advice on where I could look to help solve this problem I would much appreciate it.
In particular can some explain to me the flow of how a request is handled in this configuration. I find it pretty confusing and I would like to know how the WebAPI and Owin fit together. Not knowing this is making it me difficult for me to understand the problem.
Thanks.
For reference here is my WebAPI start up class:
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static string PublicClientId { get; private set; }
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
}
Update 1 - question correct after Darin's comments. The problem is not in the constructor.
Update 2 - Dispose Method:
protected override void Dispose(bool disposing)
{
if (disposing)
{
UserManager.Dispose();
}
base.Dispose(disposing);
}
Update 3 - Added the /Register method to show where I have a breakpoint (that's never reached)
There is no check for a null _userManager in your dispose method but the backing field can still be null. Also you access the UserManager property instead of using the backing field directly. So every time _userManager is null and the AccountController gets disposed the UserManager will try to create a new OwinContext. And that will fail.
Change your dispose method to:
protected override void Dispose(bool disposing)
{
if (disposing && _userManager != null)
{
_userManager.Dispose();
_userManager = null
}
base.Dispose(disposing);
}
The problem I have is in the Account constructor
The HTTP Context is not available in a controller constructor and this is by design. The earliest point in the execution where you can access it is after the Initialize method:
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
// This is the earliest stage where you can access the HTTP context (request, response, ...).
}

MVC user Authentication using Web API

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.

OWIN Authentication - redirect loop - The request filtering module is configured to deny a request where the query string is too long

I'm trying to use OWIN Authentication with just google authentication
ie - users of my app exist only if they have a google account
I've configured my Auth Config like this:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ExternalCookie,
CookieName = CookieAuthenticationDefaults.CookiePrefix + "External",
ExpireTimeSpan = TimeSpan.FromMinutes(5),
LoginPath = new PathString("/authentication"),
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions
{
ClientId = "xxx123",
ClientSecret = "xxx456",
});
}
}
My AuthenticationController has an Index method:
[AllowAnonymous]
public ActionResult Index()
{
Request.GetOwinContext().Authentication.Challenge(new AuthenticationProperties
{
RedirectUri = Url.Action("ExternalLoginCallback")
});
return new HttpUnauthorizedResult();
}
When I got to a restricted page, I get
HTTP Error 404.15 - Not Found The request filtering module is
configured to deny a request where the query string is too long.
... it is hitting my AuthenticationControllers Index method many many times...
Any idea what I've not configured correctly?
EDIT
My ExternalLoginCallback looks like:
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
}
Note - this method is never hit, if I put a breakpoint on it
My issue was that I wasn't passing in the provider type to the Challenge method -
Changing my Index action method to:
[AllowAnonymous]
public ActionResult Index()
{
var properties = new AuthenticationProperties
{
RedirectUri = Url.Action("ExternalLoginCallback")
};
//challenge
Request.RequestContext.HttpContext.GetOwinContext().Authentication.Challenge(properties, "Google");
//if above didn't handle it, return unauth.
return new HttpUnauthorizedResult();
}

Resources