How to switch from Hybrid flow to ResourceOwner flow with IdentityServer3 - c#

I need to upgrade (or downgrade) my Website to using a local login page. I had it all working using the hybrid flow using the following code
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions(){});
And then when the token would come back, it would give me access to complete the authentication logic in asp.net- setting the claims identity, principal, etc.
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions()
{
Notifications = new OpenIdConnectAuthenticationNotifications()
{
SecurityTokenValidated = async n =>
{
// perform transform, etc..
n.AuthenticationTicket = new AuthenticationTicket(
identity, n.AuthenticationTicket.Properties);
await Task.FromResult(0);
}
}
});
Now, I am going to be collecting the username and password from an MVC action method. I am able to get the access token from the client this way.
[HttpPost]
public ActionResult Login(LoginModel model)
{
var client = new TokenClient(
StsSettings.TokenEndpoint,
ClientId,
Secret);
var x = client.RequestResourceOwnerPasswordAsync(model.UserName, model.Password, "customid openid").Result;
return View(model);
}
But I'm not sure how the easiest way to tell ASP.NET to point to my custom login page instead of an identity server. Would I use forms authentication logic and create some AuthenticationTicket? Also, what is the best way set the ClaimsIdentity (I know how to get the claims back, just need a "hook")

If you want the outcome of the resource owner password flow to be the logged in user, you need to issue the main authentication cookie with the claims you have for that newly authenticated user.
var claims = new Claim[] {
new Claim("name", username),
new Claim("sub", "4848784904"),
new Claim("email", "BrockAllen#gmail.com"),
new Claim("role", "Admin"),
new Claim("role", "Dev"),
};
// "Cookies" is the name of your cookie middleware,
// so change to match what you're actually using in Startup.cs
var ci = new ClaimsIdentity(claims, "Cookies", "name", "role");
Request.GetOwinContext().Authentication.SignIn(ci);
return Redirect("~/Home/Secure");

Related

How to automatically refresh access token in ASP.NET Core MVC app

I have a Web API backend that has an authentication endpoint for retrieving both the access and refresh token. My client already retrieves the access token and creates a new identity to sign in the user, using the HttpContext.
But how do I automatically get a new access token using the refresh token in my ASP.NET MVC client.
I already have the entire backend working for this part.
I've tried adding the Bearer authentication scheme to my client' services.
Now I'm using the Cookie authentication scheme.
Adding the authentication to the service:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options => { options.LoginPath = "/Home/Index"; });
And here is how I log the user in:
var token = JsonConvert.DeserializeObject<Token>(result);
AuthenticationProperties options = new AuthenticationProperties()
{
AllowRefresh = true,
IsPersistent = true,
ExpiresUtc = DateTimeOffset.FromUnixTimeMilliseconds(token.Expiry),
};
// TODO: new claim for the user name
var claims = new[]
{
new Claim(ClaimTypes.Name, dto.Email),
new Claim(ClaimTypes.Role, dto.Role),
new Claim("AccessToken", $"Bearer {token.AccessToken}"),
};
var identity = new ClaimsIdentity(claims, "ApplicationCookie");
var principal = new ClaimsPrincipal(identity);
HttpContext.SignInAsync(principal, options);
Maybe this isn't the best way to authenticate my client but It worked so far.
I need the back-end and client to be separated because a mobile client also uses the same back-end.
Thanks in advance.

Persisting new claims across requests

How do I add the new claims in such a way that they persist through requests until the cookie expires?
I am using OWIN middle ware, on-premises authentication to authenticate the users logging into the system.
The sign-in part is successful, and I added Roles to the user claims provided by the ws-federation to help authorize the user for certain action methods.
At the time of login, in the controller, I have written the following to add the roles:
string[] roles = { "Role1", "Role2" };
var identity = new ClaimsIdentity(User.Identity);
foreach (var role in roles)
{
identity.AddClaim(new Claim(ClaimTypes.Role, role));
}
var authenticationManager = HttpContext.GetOwinContext().Authentication;
authenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant
(new ClaimsPrincipal(identity),
new AuthenticationProperties { IsPersistent = true });
But when I check the claims at the next request, I don't see the role claims.
After successful authentication I believe you added custom claims (normally to some event handler once successfully authenticated). Now in order to persist that information in subsequent request you need to use CookieAuthentication middle ware before your authentication owin in pipeline.
How it works :
Upon successful authentication first time and addition of custom claims, claims will be transformed into sort of auth cookie and sent back to client. Subsequent request will carry this auth cookie. CookieAuthentication middle ware on finding auth cookie will set your Thread.CurrentPriciple with claims obtained from cookie.
During first time request when cookie middle ware does see any auth cookie, it passes request to next middle ware in pipe line (Authentication owin in your case) to challenge user for login.
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationType = "Cookies",
AuthenticationMode= AuthenticationMode.Active,
CookieName="XXXXX",
CookieDomain= _cookiedomain,
/* you can go with default cookie encryption also */
TicketDataFormat = new TicketDataFormat(_x509DataProtector),
SlidingExpiration = true,
CookieSecure = CookieSecureOption.Always,
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = _clientID,
Authority = _authority,
RedirectUri = _redirectUri,
UseTokenLifetime = false,
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = SecurityTokenValidated,
AuthenticationFailed = (context) =>
{
/* your logic to handle failure*/
}
},
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
ValidIssuers = _validIssuers,
ValidateIssuer = _isValidIssuers,
}
});
EDIT: (Additional information)
Pretty much the exact code as above works for ws federation also, with the same logic and everything.
SecurityTokenValidated = notification =>
{
ClaimsIdentity identity = notification.AuthenticationTicket.Identity;
string[] roles = { "Role1", "Role2" };
foreach (var role in roles)
{
identity.AddClaim(new Claim(ClaimTypes.Role, role));
}
return Task.FromResult(0);
}
You need to use the same AuthenticationType that you used in Startup.ConfigureAuth. For example:
In Startup.ConfigureAuth:
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
//....
});
And in your login code (provided in the question):
var identity = new ClaimsIdentity(DefaultAuthenticationTypes.ApplicationCookie);
Or make sure that the User.Identity has the same AuthenticationType, and you're good to use that like you did:
var identity = new ClaimsIdentity(User.Identity);
Now the important part is that for the login, you should add the claims before singing the use in, not after. Something like this:
HttpContext.GetOwinContext().Authentication.SignIn(identity);
You can add the claims after signing in, but you will be modifying the cookie right after it is created, which is not efficient. If in some other code you need to modify the claims, then you can use something similar to your code, but you must get the context from Current:
HttpContext.Current.GetOwinContext().Authentication.AuthenticationResponseGrant =
new AuthenticationResponseGrant(new ClaimsPrincipal(identity),
new AuthenticationProperties { IsPersistent = true });
So you can fix your code by simply adding Current like above, but that's not efficient for the login code and it is better to pass the claims to the SignIn function.
you can do the following in WEB API C # (SOAP),(STORED PROCEDURES)
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
LoginModel model = new LoginModel();
//validate user credentials and obtain user roles (return List Roles)
//validar las credenciales de usuario y obtener roles de usuario
var user = model.User = _serviceUsuario.ObtenerUsuario(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "El nombre de usuario o la contraseƱa no son correctos.cod 01");
return;
}
var stringRoles = user.Roles.Replace(" ", "");//It depends on how you bring them from your DB
string[] roles = stringRoles.Split(',');//It depends on how you bring them from your DB
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
foreach(var Rol in roles)
{
identity.AddClaim(new Claim(ClaimTypes.Role, Rol));
}
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
identity.AddClaim(new Claim(ClaimTypes.Email, user.Correo));
identity.AddClaim(new Claim(ClaimTypes.MobilePhone, user.Celular));
identity.AddClaim(new Claim("FullName", user.FullName));//new ClaimTypes
identity.AddClaim(new Claim("Empresa", user.Empresa));//new ClaimTypes
identity.AddClaim(new Claim("ConnectionStringsName", user.ConnectionStringsName));//new ClaimTypes
//add user information for the client
var properties = new AuthenticationProperties(new Dictionary<string, string>
{
{ "userName",user.NombreUsuario },
{ "FullName",user.FullName },
{ "EmpresaName",user.Empresa }
});
//end
var ticket = new AuthenticationTicket(identity, properties);
context.Validated(ticket);
}

Using Identity with token and cookie authentication

Im trying to setup Token authentication with cookie authentication on same time in my application.
I created a MVC project in asp.net core 2.0, with individual user accounts to auth. Setup roles to the users too.
If i follow this tutorial of Shawn Wildermuth Two-AuthorizationSchemes-in-ASP-NET-Core-2
Everything works fine to get the Token of the registered user. But if i use the Role attribute on authorize [Authorize(Roles="Admin")] im getting a 403 response.
I think that is because the Token is not receiving the Role on auth.
How to setup this? Is any way to pass the Roles on the Token process?
To generate the token he is using this piece of code:
[AllowAnonymous]
[HttpPost]
public async Task<IActionResult> GenerateToken([FromBody] LoginViewModel model) { if (ModelState.IsValid) {
var user = await _userManager.FindByEmailAsync(model.Email);
if (user != null)
{
var result = await _signInManager.CheckPasswordSignInAsync(user, model.Password, false);
if (result.Succeeded)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(_config["Tokens:Issuer"],
_config["Tokens:Issuer"],
claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: creds);
return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
}
} }
return BadRequest("Could not create token"); }
You guys have any idea?
Thanks
If you add the following using and code, that should help.
using System.Security.Claims;
...
var userRoles = await _userManager.GetRolesAsync(user);
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
}.Union(userRoles.Select(m => new Claim(ClaimTypes.Role, m)));
You can see the Union that adds the roles in with the type of ClaimTypes.Role, this will enable them to be used in the AuthorizeAttribute
HTH

How to C# Core OAuth without external login panel

I'm making an API for Exact Online for a website with a form. The visitor will fill in his information and after that the visitor sends it. It need to be send to the Exact online account from my client. But before that I need a accesstoken. The problem is that I don't want to give the user the login page that Exact gives me. I'm searching already for days to find a way to skip the login or to enter the login information by backend (there is always 1 login, and that is the login from my client).
Now this authorization thing is something new for me. So far I know I can call my authorization settings from the startup with this:
HttpContext.Authentication.GetAuthenticateInfoAsync("ExactOnline");
But then I get that loginscreen that I don't want. The only thing that Exact is telling me to do:
Create an app registration that supports an automated connection wizard (your provisioning process).
Is there a way to send them the login information and the visitor doesn't see a loginpage.
In my Startup.cs
var s = new OAuthOptions{
AuthenticationScheme = "ExactOnline",
ClientId = "CLIENTID",
ClientSecret = "CLIENTSECRET",
CallbackPath = new PathString("/callback"),
AutomaticAuthenticate = true,
AutomaticChallenge = true,
AuthorizationEndpoint = new Uri(string.Format("{0}/api/oauth2/auth", "https://start.exactonline.nl")).ToString(),
TokenEndpoint = new Uri(string.Format("{0}/api/oauth2/token", "https://start.exactonline.nl")).ToString(),
//Scope = { "identity", "roles" },
Events = new OAuthEvents
{
OnCreatingTicket = context =>
{
context.Identity.AddClaim(new Claim("urn:token:exactonline", context.AccessToken));
return Task.FromResult(true);
}
}
};
app.UseOAuthAuthentication(s);
First i had this code, but that gives me a null exception when i put the identity in the claimprincipal, probably because my claimsprincipal is null and i don't know why.
HttpContext.Authentication.AuthenticateAsync("ExactOnline");
var identity = new ClaimsIdentity("ExactOnline",ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
identity.Label = "Authentication";
identity.AddClaim(new Claim(ClaimTypes.Name, "USERNAME?"));
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, "PASSWORD?"));
claimsPrincipal.AddIdentity(identity);
var test = HttpContext.Authentication.SignInAsync("ExactOnline", claimsPrincipal, new AuthenticationProperties() { IsPersistent = false }));
After that i tried following code, but that also didn't work. My code will continue, but the test variable will be filled with this message: The name 'InnerExceptionCount' does not exist in the current context.
var identity = new ClaimsIdentity("ExactOnline", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
identity.Label = "Authentication";
identity.AddClaim(new Claim("username", "USERNAME"));
identity.AddClaim(new Claim("password", "PASSWORD"));
ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(identity);
var test = HttpContext.Authentication.SignInAsync("ExactOnline", claimsPrincipal, new AuthenticationProperties() { IsPersistent = false });
Someone know how to solve this problem?

How to get user id from IdentityServer in client app when including access token?

I have implemented an authentication service based on IdentityServer3 and a simple MVC client app and a Shopper API secured by the authentication service. I've implemented a IdentityServer custom UserService so that the authentication service authenticates against our existing user data store. My Shopper API expects a userid in the Shopper Get request. Currently the response from authentication service includes the identity token and the access token, but no user id. I tried adding a user_id claim in the AuthenticationResult from my custom UserService.AuthenticateLocalAsync method, but I'm not seeing it in my client app code.
UserService.AuthenticateLocalAsync looks like this:
try
{
var user = new shopper(_dbConnLib, context.UserName, context.Password);
var claims = new List<Claim> { new Claim("user_id", user.shopperid) };
context.AuthenticateResult = new AuthenticateResult(user.shopperid, user.MemberDetail.billToAddress.FirstName, claims);
}
catch(shopperInitFromException ex)
{
context.AuthenticateResult = null; // Indicates username/password failure
}
return Task.FromResult(0);
And my client app SecurityTokenValidated handler looks like this:
SecurityTokenValidated = async n =>
{
var nid = new ClaimsIdentity(
n.AuthenticationTicket.Identity.AuthenticationType,
Constants.ClaimTypes.GivenName,
Constants.ClaimTypes.Role);
var userInfoClient = new UserInfoClient(
new Uri(n.Options.Authority + "/connect/userinfo").ToString());
var userInfo = await userInfoClient.GetAsync(n.ProtocolMessage.AccessToken);
userInfo.Claims.ToList().ForEach(ui => nid.AddClaim(new Claim(ui.Type, ui.Value)));
nid.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
nid.AddClaim(new Claim("access_token", n.ProtocolMessage.AccessToken));
//nid.AddClaim(new Claim("user_id", n.ProtocolMessage.UserId));
nid.AddClaim(new Claim("expires_at", DateTimeOffset.Now.AddSeconds(int.Parse(n.ProtocolMessage.ExpiresIn)).ToString()));
n.AuthenticationTicket = new AuthenticationTicket(
nid,
n.AuthenticationTicket.Properties);
}
If I step through that in the debugger, userInfo.Claims always has a count of 0. How can I get back a claim with the unique identifier of the user? Or can I get it from the identity or access token? Or should I just pass the tokens to the Shopper API and let it determine the id from the tokens?
I think I may have the answer. So far, as far as I can tell, the claims I include in the AuthenticateResult constructor in my override of AuthenticateLocalAsync don't seem to go anywhere. But the claims I include in my override of GetProfileDataAsync appear in the token. My GetProfileDataAsync code, which appears to set the claims properly, looks like this:
public override Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var user = new shopper(_dbConnLib, context.Subject.FindFirst("sub").Value);
var claims = new List<Claim> { new Claim("sub", user.shopperid), new Claim("acr_level", "level 0"), new Claim("amr", "anonymous") };
context.IssuedClaims = claims;
return Task.FromResult(0);
}
My AuthenticateLocalAsync code that sets claims in the AuthenticateResult that I never see in my client app code looks like this:
public override Task AuthenticateLocalAsync(LocalAuthenticationContext context)
{
// TODO: Handle AddshopperToBasketException in UserService.AuthenticateLocalAsync
try
{
var user = new shopper(_dbConnLib, context.UserName, context.Password);
var claims = new List<Claim> { new Claim("acr_level", "level 0"), new Claim("amr", "anonymous") };
context.AuthenticateResult = new AuthenticateResult(user.shopperid, user.MemberDetail.billToAddress.FirstName, claims);
}
catch(shopperInitFromException ex)
{
context.AuthenticateResult = null; // Indicates username/password failure
}
return Task.FromResult(0);
}

Categories