.NET Core 2.0 Identity AND jwt? - c#

I've been looking around and trying to do more research on .NET Core Identity (https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-2.1&tabs=visual-studio%2Caspnetcore2x) and Jwt (json web tokens). I've been rolling with the default Identity as authentication/authorization in my .NET Core 2.0 app and it has been working well so far.
I'm running into a roadblock and I think it's the way of my understanding of .NET Core identity and jwt. My application has MVC and an web api. I would ideally like to secure the web api, but I hear the best way to do that now is through jwt. Good - cool.
I can go ahead and configure jwt and then use it as my authentication/authorization (https://blogs.msdn.microsoft.com/webdev/2017/04/06/jwt-validation-and-authorization-in-asp-net-core/), but - do I need to go ahead and spin up a new server to serve as the authorization server for jwt? If so, I'm not going to do that (too expensive).
What about my .NET Core identity code if I do go with jwt? Does that have to go away then? If it can co-exist, how might I authorize my MVC pages with Identity and my api endpoints with jwt?
I realize this is an open-ended question, but the core of it is:
Can .NET Core Identity and JWT co-exist? Or do I have to choose one or the other? I have MVC and an web api and would like to secure both.

Yes, you can.
The logic process is in this method:
Step 1: GetUserClaims
var identity = await GetClaimsIdentity(credentials.UserName, credentials.Password);
Into GetClaimsIdentity you will
private async Task<ClaimsIdentity> GetClaimsIdentity(string userName, string password)
{
if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password))
return await Task.FromResult<ClaimsIdentity>(null);
var userToVerify = await _userManager.FindByNameAsync(userName);
if (userToVerify == null) {
userToVerify = await _userManager.FindByEmailAsync(userName);
if (userToVerify == null) {
return await Task.FromResult<ClaimsIdentity>(null);
}
}
// check the credentials
if (await _userManager.CheckPasswordAsync(userToVerify, password))
{
_claims = await _userManager.GetClaimsAsync(userToVerify);
return await Task.FromResult(_jwtFactory.GenerateClaimsIdentity(userToVerify.UserName, userToVerify.Id, _claims));
}
// Credentials are invalid, or account doesn't exist
return await Task.FromResult<ClaimsIdentity>(null);
}
Step 2: Group all user claims you need add to the token - Use System.Security.Claims
public ClaimsIdentity GenerateClaimsIdentity(string userName, string id, IList<Claim> claims)
{
claims.Add(new Claim(Helpers.Constants.Strings.JwtClaimIdentifiers.Id, id));
// If your security is role based you can get then with the RoleManager and add then here as claims
// Ask here for all claims your app need to validate later
return new ClaimsIdentity(new GenericIdentity(userName, "Token"), claims);
}
Step 3: Then back on your method you have to generate and return the JWT Token
jwt = await jwtFactory.GenerateEncodedToken(userName, identity);
return new OkObjectResult(jwt);
To generate token do something like this:
public async Task<string> GenerateEncodedToken(string userName, ClaimsIdentity identity)
{
List<Claim> claims = new List<Claim>();
//Config claims
claims.Add(new Claim(JwtRegisteredClaimNames.Sub, userName));
claims.Add(new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()));
claims.Add(new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(), ClaimValueTypes.Integer64));
//End Config claims
claims.AddRange(identity.FindAll(Helpers.Constants.Strings.JwtClaimIdentifiers.Roles));
claims.AddRange(identity.FindAll("EspecificClaimName"));
// Create the JWT security token and encode it.
var jwt = new JwtSecurityToken(
issuer: _jwtOptions.Issuer,
audience: _jwtOptions.Audience,
claims: claims,
notBefore: _jwtOptions.NotBefore,
expires: _jwtOptions.Expiration,
signingCredentials: _jwtOptions.SigningCredentials);
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
return encodedJwt;
}
There are many ways to do this.
The most common is:
Validate Identity User --> Get User identifiers --> Generate and Return Token Based on Identifiers --> Use Authorization for endpoints
Hope this help

You can validate the username and password and generate the Jwt.
First, make sure your API has the following default identity set up in the startup.cs:
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
Secondly, you can validate the login with something like this:
You can set up an API controller something like this:
[ApiController, Route("check")]
public class TokenController : ControllerBase
{
private readonly SignInManager<IdentityUser> signin;
public TokenController(SignInManager<IdentityUser> signin)
{
this.signin = signin;
}
[HttpGet]
public async Task<string> Get(string user, string pass)
{
var result = await signin.PasswordSignInAsync(user, pass, true, false);
if (result.Succeeded)
{
string token = "";
return token;
}
return null;
}
}
Within your get function, you can now generate your Jwt.

Related

Azure AD B2C with WebApi2- calling GraphAPI after authentication

I have a WebApi2 app which servers as api for my app frontend. Now i want to use AD B2C to manage my users - let's say I want to differentiate them by their roles (admin or customer) and for that i created two b2c users groups accordingly. When user logs in i want to display different things for users with different roles (groups).
I'm using this example to setup Startup.Auth.cs in my WebApi2 project:
var tvps = new TokenValidationParameters
{
ValidAudience = clientId,
AuthenticationType = signUpSignInPolicy,
};
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
AccessTokenFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider(String.Format(aadInstance, tenant, defaultPolicy))),
});
From what I have read b2c doesn't return user's grups in claims for now. Some people suggested I need to call GraphApi after obtaining token to fetch these groups and add them to user's claims:
private static async Task<string> GetGroups(string token, string userId)
{
using (var client = new HttpClient())
{
var requestUrl = $"https://graph.microsoft.com/v1.0/users/{userId}/memberOf?$select=displayName";
var request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await client.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
return responseString;
}
}
This is where I've stuck. How can I inject my code to get token for calling graph? I've messed with OAuthBearerAuthenticationOptions.Provider:
Provider = new OAuthBearerAuthenticationProvider
{
OnValidateIdentity = (context) =>
{
// var token = ??
// var userId = <get from context's claims>
// var groups = GetGroups(token, userId);
// <add to claims>
return Task.CompletedTask;
}
},
...but I don't know how to get to token. Maybe that's wrong from the start and I need another approach?
Customer's token cannot be used to call AADGraph/MSGraph Apis. To get token to call graph apis in an automated way, we need app-only access. We need to configre an app in the tenant, the crendetial of which are used to get a token. That token can then be used to call memberOF Api (or any other api which does or require user information to be there)
Here is the sample and explaination of how to call AAD Graph apis in a B2C dependent service.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-devquickstarts-graph-dotnet

How to write Integration Test for Identity Server 4 in asp.net mvc core

I am having a web api using Identity server 4. I dont know where to start writing Integration test. I am having a Login Controller taking in Username and password which is used for ResourceOwnerPassword Grant type. Below is my code.
Controller.
[Route("Authentication/Login")]
public async Task<IActionResult> WebApiLogin(string username, string password)
{
var accessToken = await UserAccessToken.GenerateToken(username, password);
return new JsonResult(accessToken);
}
Class to generate token
public async Task<string> GenerateToken(string username, string password)
{
//discover endpoint for metadata
var disco = await DiscoveryClient.GetAsync("http://localhost:5000");
//request token
var clientToken = new TokenClient(disco.TokenEndpoint, "client", "secret");
//var tokenResponse = await clientToken.RequestClientCredentialsAsync("Payment");
var tokenResponse = await clientToken.RequestResourceOwnerPasswordAsync(username, password, "IntegrapayAPI");
if (tokenResponse.IsError)
{
//Error tokenResponse.Error
return tokenResponse.Error;
}
return tokenResponse.Json.ToString();
}
IdentityServer Project startup class.
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients());
//.AddTestUsers(Config.GetUsers());
services.AddTransient<IResourceOwnerPasswordValidator, ResourceOwnerPasswordValidator>();
// Add framework services.
//services.AddMvc();
}
You can take a look at this answer: https://stackoverflow.com/a/39409789/147041
Disclaimer: my own question, my answer.
It contains a link to a GitHub repo where integration tests are set up against an API, but it will work for MVC as well of course. The essence is to use an in-memory IdentityServer to act as your token generator and validator.
Besides that, you should not mix your API with IdentityServer. Use IdentityServer to generate your tokens, then your API will validate those tokens agains the identityserver.
There are a lot of good samples out there to get you started.

How to switch from Hybrid flow to ResourceOwner flow with IdentityServer3

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

Return JWT Token generated by OAuthAuthorizatioServer from controller in Web API

Following #Taiseer Joudeh I was able to create simple POC of Web API. I'm able to create new account, then log-in and call secure Web API when I add JWT token to header.
I'd like to modify method that is responsible for creating accounts.
Right now I'm returning Create (201) code with new user object, but instead I'd like to return access token.
I've found similar question but it requires creating HttpClient and doing request to OAuthAuthorizatioServer TokenEndpointPath.
Second question I found requires generating temporary token that is returned to front-end, but then front-end must do additional request to server to get "real" token.
What I'd like to do is to return login response (access_token, token_type and expires_in) when I create user account.
I want user to be authenticated when his account is created.
I'm using just Web API and JWT without any cookies.
EDIT: My temporary solution:
after creating user I'm doing this:
var validTime = new TimeSpan(0, 0, 0, 10);
var identity = await UserManager.CreateIdentityAsync(user, "JWT");
var jwtFormat = new CustomJwtFormat(ApplicationConfiguration.Issuer);
var authenticationProperties = new AuthenticationProperties { IssuedUtc = DateTimeOffset.UtcNow, ExpiresUtc = DateTimeOffset.UtcNow.Add(validTime) };
var authenticationTicket = new AuthenticationTicket(identity, authenticationProperties);
var token = jwtFormat.Protect(authenticationTicket);
var response = new
{
access_token = token,
token_type = "bearer",
expires_in = validTime.TotalSeconds.ToInt()
};
return Ok(response);
where CustomJwtFormat comes from this awesome article.
Below is some code similar to what I'm doing in my application, which is using Asp.Net Core 1.0. Your signin and user registration will differ if you're not using Core 1.0.
public async Task<string> CreateUser(string username, string password)
{
string jwt = String.Empty;
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
}
var user = await _userManager.FindByNameAsync(username);
if (user == null) // user doesn't exist, create user
{
var newUser = await _userManager.CreateAsync(new ApplicationUser() { UserName = username }, password);
if (newUser.Succeeded) //user was successfully created, sign in user
{
user = await _userManager.FindByNameAsync(username);
var signInResult = await _signInManager.PasswordSignInAsync(user, password, false, true);
if (signInResult.Succeeded) //user signed in, create a JWT
{
var tokenHandler = new JwtSecurityTokenHandler();
List<Claim> userClaims = new List<Claim>();
//add any claims to the userClaims collection that you want to be part of the JWT
//...
ClaimsIdentity identity = new ClaimsIdentity(new GenericIdentity(user.UserName, "TokenAuth"), userClaims);
DateTime expires = DateTime.Now.AddMinutes(30); //or whatever
var securityToken = tokenHandler.CreateToken(
issuer: _tokenOptions.Issuer, //_tokenAuthOptions is a class that holds the issuer, audience, and RSA security key
audience: _tokenOptions.Audience,
subject: identity,
notBefore: DateTime.Now,
expires: expires,
signingCredentials: _tokenOptions.SigningCredentials
);
jwt = tokenHandler.WriteToken(securityToken);
Response.StatusCode = (int)HttpStatusCode.Created;
await _signInManager.SignOutAsync(); //sign the user out, which deletes the cookie that gets added if you are using Identity. It's not needed as security is based on the JWT
}
}
//handle other cases...
}
}
Basically, the user is created and then signed in automatically. I then build a JWT (add in any claims you want) and return it in the response body. On the client side (MVC and Angular JS) I get the JWT out of the response body and store it. It is then passed back to the server in the Authorization header of each subsequent request. Authorization policies for all server actions are based on the set of claims supplied by the JWT. No cookies, no state on the server.
EDIT: I suppose you don't even need to call the signIn method in this process as you can just create the user, create a JWT, and return the JWT. When a user logs in on a future request you would use the Sign-In, create token, Sign-Out approach.
The idea of sending a response with access_token, token_type and expires_in, when the user is created is a great idea. But, I would stick to calling the "/token" end point with HttpClient to achieve this task. There are quite a few security checks that needs to be performed before token is generated. Since this is security I would not take any risk. I feel comfortable using the libraries/ code provided by industry experts.
That said, I tried to come up with a class you can call to create the token once the user is created. This code has been taken from the Microsoft's implementation of OAuth Authorization Server in their Katana project. You can access the source here. As you can see there is quite a lot happening when creating the token.
Here is the modified version of that middleware class for generating the token. You have to provide the proper OAuthAuthorizationServerOptions, Context, username, password, Scopes and clientid to get an access token. Please note that this is a sample implementation to guide you in the right direction. Please test it thoroughly if you want to use this.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using AspNetIdentity.WebApi.Providers;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Infrastructure;
using Microsoft.Owin.Security.OAuth;
namespace WebApi.AccessToken
{
public class TokenGenerator
{
public string ClientId { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public IList<string> Scope { get; set; }
private OAuthAuthorizationServerOptions Options { get; } =
new OAuthAuthorizationServerOptions()
{
//For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new CustomOAuthProvider(),
AccessTokenFormat = new CustomJwtFormat("http://localhost:59822")
};
public async Task<IList<KeyValuePair<string, string>>> InvokeTokenEndpointAsync(IOwinContext owinContext)
{
var result = new List<KeyValuePair<string, string>>();
DateTimeOffset currentUtc = Options.SystemClock.UtcNow;
// remove milliseconds in case they don't round-trip
currentUtc = currentUtc.Subtract(TimeSpan.FromMilliseconds(currentUtc.Millisecond));
AuthenticationTicket ticket = await InvokeTokenEndpointResourceOwnerPasswordCredentialsGrantAsync(owinContext, Options, currentUtc);
if (ticket == null)
{
result.Add(new KeyValuePair<string, string>("ERROR", "Failed to create acess_token"));
return result;
}
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(Options.AccessTokenExpireTimeSpan);
ticket = new AuthenticationTicket(ticket.Identity, ticket.Properties);
var accessTokenContext = new AuthenticationTokenCreateContext(
owinContext,
Options.AccessTokenFormat,
ticket);
await Options.AccessTokenProvider.CreateAsync(accessTokenContext);
string accessToken = accessTokenContext.Token;
if (string.IsNullOrEmpty(accessToken))
{
accessToken = accessTokenContext.SerializeTicket();
}
DateTimeOffset? accessTokenExpiresUtc = ticket.Properties.ExpiresUtc;
result.Add(new KeyValuePair<string, string>("access_token", accessToken));
result.Add(new KeyValuePair<string, string>("token_type", "bearer"));
TimeSpan? expiresTimeSpan = accessTokenExpiresUtc - currentUtc;
var expiresIn = (long)expiresTimeSpan.Value.TotalSeconds;
if (expiresIn > 0)
{
result.Add(new KeyValuePair<string, string>("expires_in", "bearer"));
}
return result;
}
private async Task<AuthenticationTicket> InvokeTokenEndpointResourceOwnerPasswordCredentialsGrantAsync(IOwinContext owinContext, OAuthAuthorizationServerOptions options, DateTimeOffset currentUtc)
{
var grantContext = new OAuthGrantResourceOwnerCredentialsContext(
owinContext,
options,
ClientId,
UserName,
Password,
Scope);
await options.Provider.GrantResourceOwnerCredentials(grantContext);
return grantContext.Ticket;
}
}
}
Please let me know if you have any questions.
Thank you,
Soma.
I assume you are referring to the following article: http://bitoftech.net/2015/02/16/implement-oauth-json-web-tokens-authentication-in-asp-net-web-api-and-identity-2/
The general approach to authenticating a user in this case is
Make an HTTP call to the token endpoint
User Enters the credentials at the UI that is rendered
IdP verifies the credentials and issues a token
User makes another call to an authorized endpoint and OWIN will validate this (JWT) token (as configured in the ConfigureOAuthTokenConsumption method) and if successfull will set a user session with expiry same as the token expiry. The session is set using session cookies.
Now try to understand that in general, this whole process of authentication is required because your server does not trust the user logging in to be the user the person is claiming to be. However, in your case, you (or your server code) just created a user and you know for sure that the person accessing your website is the user you have just created. In this case you don't need to validate a token to create a session for this user. Just create a session with an expiry that suits your use-case.
The next time the user will have to log-in and prove him/herself to the server using a token but this time the user does not need to prove him/her self.
Note: If you absolutely are adamant about requiring a token to log in a user who you yourself have just created using their credentials here are a couple of issues.
You are taking on the responsibility of storing (having access to) the user credentials, which might not be with you over the lifetime of the application (in most cases you might want to act as a relying party rather than an IdP).
Even if you want to then doing it is not trivial. You will have to make the calls to the token end point in code (server or client side) on behalf of the user, enter their credentials for them, retrieve the token, call an authenticated endpoint on your site and retrieve the session cookie all while hiding all this from the user, which probably is something you will either do if you hate yourself :) but also isn't very secure way of doing things especially when you are taking all the trouble to implement OAuth in the first place.
Also, take a look at Windows Server 2016 (technical preview 5 at this time) which supports implicit grants and might take writing all this custom code off your plate if you can wait a bit for RTM.
In an OAuth solution you as a developer are are not required to handle the cookie setting yourself. The cookie handling is done for you automatically by the framework.
Also the only way to set a session is a. Using session cookies or b. Use cookie-less (in the url) methods. Look at http://www.cloudidentity.com/blog/2015/02/19/introducing-adal-js-v1/ for more details on token validation and session establishment (also search the term cookie and you will know what all it's used for).
If you start thinking about not using cookies at all not only will you have to figure out how to maintain session and do it securely without cookies but also have to re-write the token refresh code that detects and refreshes the token for you based on the presence of a session cookie. (i.e. not a smart idea)
I am using the exact technology stack and recently implemented token based authorization successfully. The link I took reference from had very neatly defined the token-based auth in Web APIs. A must bookmark page I must say. Here is the link: TOKEN BASED AUTHENTICATION IN WEB APIs.

Web API Bearer tokens - can I use custom tokens?

I'm securing a Web API site, and I want to use tokens. But, I'm working with a legacy database, where there is a users table and each user already has a token created for them and stored in the table.
I'm trying to work out if I can use the Identity oAuth bearer token auth bits, but plug it all into my existing database, so that
Granting a token just returns the token for that user from the db
I can validate the token by looking it up in the db and creating an identity from the user (I am using ASP.NET Identity elsewhere in the site for the MVC side of things)
I can't work out if this is going to be possible, or if I should give up and use a standard HTTP handler approach. Here's my fairly standard code so far, which just issues standard tokens, not the existing ones I want to work with.
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider()
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
var bearerAuth = new OAuthBearerAuthenticationOptions()
{
Provider = new OAuthBearerAuthenticationProvider()
};
app.UseOAuthBearerAuthentication(bearerAuth);
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
var manager = new UserManager<User, long>(new UserStore(new UserRepository()));
var user = await manager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
}
else
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("name",user.Email));
context.Validated(identity);
}
}
}
Answering my own question ;)
Yes, it is possible. It mostly requires that you sort out a custom Token provider and implement your logic in there. A good sample of this:
https://github.com/eashi/Samples/blob/master/OAuthSample/OAuthSample/App_Start/Startup.Auth.cs

Categories