Use Windows Auth to authenticate user and then generate JWT in ASPNET Core 2.1 - asp.net-core

I have an ASP.NET Core 2.1 WebApi, in which I have implemented JWT authentication. The user calls api/authentication/authenticate, passes their username/password in the message body, and gets back a JWT in return which they then use to access the service.
I also need the API to accept Windows authentication -- the user will call api/authentication/windows passing no user information, the service will check they are in the list of authorized users as listed in the web.config file (if I am hosting in IIS). If so, return a JWT token and the user can use that to access the service.
Currently I'm thinking about this...
The api/authentication/windows method will get the username from the request
Check the username against the list of authorized users. If they are on it, return a token. If not, go to (3)
Check against any groups in the authorized users list. If they are a member, return a token. If not, return a 401 Unauthorized error
Is this the correct way to approach this?
Very similar (unanswered) question here: Generate JWT token on successful authentication with Windows Authentication

If you want to enable both JWT and AD authentication ,in my option, you still need to validate the user's credential(username/password) against Active Directory in web api :
https://www.brechtbaekelandt.net/blog/post/authenticating-against-active-directory-with-aspnet-core-2-and-managing-users
Pass just username won't work since there is no authenticated user context in web api .
After validating user credential , you can generate jwt token as usual , for example if using HS256:
private string BuildToken()
{
var claims = new[] {
new Claim(JwtRegisteredClaimNames.NameId,"name1"),
new Claim(JwtRegisteredClaimNames.Sub,"name1"),
new Claim("customer","customer1"),
new Claim(JwtRegisteredClaimNames.Email,"wuxiyuan#sina,com"),
new Claim("role","user"),
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Youkey"));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken("name1",
"name1",
claims,
expires: DateTime.Now.AddDays(1),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}

To get the username from the request to the route api/authentication/windows you should activate windows authentication for the asp.net core application. You can achieve that either modifying the web.config or enable the windows authentication in IIS.
<configuration>
<system.webServer>
<security>
<authentication>
<anonymousAuthentication enabled="true" />
<windowsAuthentication enabled="true" />
</authentication>
</security>
</system.webServer>
</configuration>
For debugging purposes modify launchSettings.json:
"iisSettings": {
"windowsAuthentication": true,
}
Leave the anonymous authentication activated: <anonymousAuthentication enabled="true" />. It is necessary in order the JWT authentication works properly for the route api/authentication/authenticate
Make sure that the attribute forwardWindowsAuthToken of the aspNetCore element in web.config is not deactivated: forwardWindowsAuthToken="true" or remove it because of the default value (true)
Add IISIntegration to the webHostBuilder unless you use a default builder: WebHost.CreateDefaultBuilder(args) - UseIISIntegration is called implicit within this extension method.
Add an Authorize attribute for the POST-method which will be mapped with the route api/authentication/windows
Test the authentication (sending windows-credentials):
var handler = new System.Net.Http.HttpClientHandler()
{
Credentials = System.Net.CredentialCache.DefaultCredentials
};
var httpClient = new System.Net.Http.HttpClient(handler)
{
BaseAddress = new Uri("http://localhost")
};
var response = await httpClient.PostAsync("api/authentication/windows", null);
or using XMLHttpRequest object:
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://localhost/api/authentication/windows', true);
xhr.withCredentials = true;
xhr.send();
Get the user name in the controller:
var username = HttpContext.User.FindFirst(System.Security.Claims.ClaimTypes.Name)?.Value;
Generate a JWT-Token, e.g using jose-jwt:
var claims = new Dictionary<string, object>
{
["jti"] = Guid.NewGuid(),
["sub"] = username,
["exp"] = DateTimeOffset.UtcNow.AddMinutes(100).ToUnixTimeSeconds()
};
var secretKey = new byte[] { 164, 60, 194, 0, 161 };
var headers = new Dictionary<string, object>
{
["alg"] = "HS512",
["typ"] = "JWT"
};
var token = JWT.Encode(claims, secretKey, JwsAlgorithm.HS512, headers);

Related

Identity server 4: get access toke on asp.net webform .NET 4.5

I am using Identity serve4 for user authentication and authorization and one of my clients is asp.net webform written on .NET 4.5. When the user tried to access the protected webform I am redirecting the user to identity server for authentication. But after authentication, there is a logic which based on currently logged userid, and for getting the current user login info I have to call some token endpoint which requires the token? I inspected the webform after login and I noticed there are some auth cookies. Now my question is how do I get token from this? On asp.net core we get an access token using the httpcontext below method but how do we get the same on asp.net webform using .NET 4.5?
var access_token = await HttpContext.GetTokenAsync("access_token")
The easiest way would be to save the access token in the cookie after authentication. Change the code on client to be like this:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ResponseType = "id_token token",
Scope = "openid profile api1",
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = n =>
{
n.AuthenticationTicket.Identity.AddClaim(new Claim("access_token", n.ProtocolMessage.AccessToken));
return Task.FromResult(0);
},
}
});
And then you can retrieve the access_token from current user's claims, like this:
var accessToken = user.FindFirst("access_token").Value;
I explained it with details here: https://nahidfa.com/posts/identityserver4-and-asp-.net-mvc/#call-an-authorized-api-endpoint-using-access-token
Edit:
To set Identity.Name add this code:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
...
TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
NameClaimType = "name"
}, // This is to set Identity.Name
});

How to keep application sessions seperate on same domain?

I have been working on a problem for about a month now in regards to JWT authentication. I have multiple Asp.net MVC 4.5 applications using Owin that are deployed on the same machine and have different subdomains using IIS with an integrated pipeline. My problem is that when I log in to one application and browse to another application it does not re-authenticate and automatically allows the user to pass the authorization filter.
I have tried setting the cookie policy to strict and the cookie domain, path and name to different values and am unable to resolve this problem.
I have one custom Authentication Library that each of these applications use. I hope you can help.
What I would like to happen is that each application maintain its own authentication session and just because the user authenticated against the AD domain, it requires the user to reauthorize to make sure they have access to a particular application.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieName = $"{tenantId}{clientId}"
})
.UseOpenIdConnectAuthentication(GetAuthenticationOptions(clientId, tenantId, clientSecret));
private static OpenIdConnectAuthenticationOptions GetAuthenticationOptions(string clientId, string tenantId, string clientSecret)
{
return new OpenIdConnectAuthenticationOptions
{
MetadataAddress = string.Format("https://login.microsoftonline.com/{0}/.well-known/openid-configuration", tenantId),
ClientId = clientId,
ClientSecret = clientSecret,
ResponseType = "id_token token",
Resource = "https://graph.microsoft.com",
UseTokenLifetime = true,
AuthenticationMode = AuthenticationMode.Active,
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = SecurityTokenValidated
},
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "mailNickName",
}
};
}
Try to set the "cookieName" attribute of the "sessionState" XML element in your web.config to different values for each of your applications. That will keep them from using the same session ID.
<system.web>
<sessionState regenerateExpiredSessionId="false" cookieless="UseCookies" cookieName="id" />
</system.web>
sessionState Element

Web site Authentication against Web API

I have the following scenario with net core 3. A web site with a login page. This login page sends the user and password to a Web API that response with a JWT token if the credentials are correct.
How can I set now my web user as authenticated? how can I set the claims of the web user with the claims I recieve from the API token?
Is it neccessary to add any service on the startup of something similar?
Could you provide me with any basic sample of how to do it or any documentation?
Thank you
You can use cookie authentication :
In the Startup.ConfigureServices method, create the Authentication Middleware services with the AddAuthentication and AddCookie methods:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Account/Login";
});
And enable middleware in Configure :
app.UseAuthentication();
app.UseAuthorization();
And in the action which user post credential to , you can send a http request to web api with credential , web api will validate the credential and return back jwt token , your web application then decode token and sign in user like :
var stream = "[token]";
var handler = new JwtSecurityTokenHandler();
var tokenS = handler.ReadToken(stream) as JwtSecurityToken;
var claimsIdentity = new ClaimsIdentity(
tokenS.Claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties
{
RedirectUri = "/Home/Privacy",
};
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);
Depending on your front end solution, you need to figure out how to decode the JWT that you received to retrieve the values that you need.
Here are a couple of things, again depending on what you are using on the front end
C#
https://developer.okta.com/blog/2019/06/26/decode-jwt-in-csharp-for-authorization
NPM Package for SPA
https://www.npmjs.com/package/jwt-decode
Here is another good resource for JWT
https://jwt.io/
You can take the JWT you received to view the values that are in it

Trouble getting ClaimsPrincipal populated when using EasyAuth to authenticate against AAD on Azure App Service in a Asp.Net Core web app

We have a web app built on Asp.Net core. It doesn't contain any authentication middleware configured in it.
We are hosting on Azure App Service and using the Authentication/Authorization option (EasyAuth) to authenticate against Azure AD.
The authentication works well - we get the requisite headers inserted and we can see the authenticated identity at /.auth/me. But the HttpContext.User property doesn't get populated.
Is this a compatibility issue for Asp.Net core? Or am I doing something wrong?
I've created a custom middleware that populates the User property until this gets solved by the Azure Team.
It reads the headers from the App Service Authentication and create a a user that will be recognized by the [Authorize] and has a claim on name.
// Azure app service will send the x-ms-client-principal-id when authenticated
app.Use(async (context, next) =>
{
// Create a user on current thread from provided header
if (context.Request.Headers.ContainsKey("X-MS-CLIENT-PRINCIPAL-ID"))
{
// Read headers from Azure
var azureAppServicePrincipalIdHeader = context.Request.Headers["X-MS-CLIENT-PRINCIPAL-ID"][0];
var azureAppServicePrincipalNameHeader = context.Request.Headers["X-MS-CLIENT-PRINCIPAL-NAME"][0];
// Create claims id
var claims = new Claim[] {
new System.Security.Claims.Claim("http://schemas.microsoft.com/identity/claims/objectidentifier", azureAppServicePrincipalIdHeader),
new System.Security.Claims.Claim("name", azureAppServicePrincipalNameHeader)
};
// Set user in current context as claims principal
var identity = new GenericIdentity(azureAppServicePrincipalIdHeader);
identity.AddClaims(claims);
// Set current thread user to identity
context.User = new GenericPrincipal(identity, null);
};
await next.Invoke();
});
Yes, this is a compatibility issue. ASP.NET Core does not support flowing identity info from an IIS module (like Easy Auth) to the app code, unfortunately. This means HttpContext.User and similar code won't work like it does with regular ASP.NET.
The workaround for now is to invoke your web app's /.auth/me endpoint from your server code to get the user claims. You can then cache this data as appropriate using the x-ms-client-principal-id request header value as the cache key. The /.auth/me call will need to be properly authenticated in the same way that calls to your web app need to be authenticated (auth cookie or request header token).
I wrote a small basic middleware to do this. It will create an identity based off of the .auth/me endpoint. The identity is created in the authentication pipeline so that [authorize] attributes and policies work with the identity.
You can find it here:
https://github.com/lpunderscore/azureappservice-authentication-middleware
or on nuget:
https://www.nuget.org/packages/AzureAppserviceAuthenticationMiddleware/
Once added, just add this line to your startup:
app.UseAzureAppServiceAuthentication();
The following code decrypts the AAD token from the Azure App Service HTTP header and populates HttpContext.User with the claims. It's rough as you'd want to cache the configuration rather than look it up on every request:
OpenIdConnectConfigurationRetriever r = new OpenIdConnectConfigurationRetriever();
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(options.Endpoint, r);
OpenIdConnectConfiguration config = await configManager.GetConfigurationAsync();
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKeys = config.SigningKeys.ToList(),
ValidateIssuer = true,
ValidIssuer = config.Issuer,
ValidateAudience = true,
ValidAudience = options.Audience,
ValidateLifetime = true,
ClockSkew = new TimeSpan(0, 0, 10)
};
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
ClaimsPrincipal principal = null;
SecurityToken validToken = null;
string token = context.Request.Headers["X-MS-TOKEN-AAD-ID-TOKEN"];
if (!String.IsNullOrWhiteSpace(token))
{
principal = handler.ValidateToken(token, tokenValidationParameters, out validToken);
var validJwt = validToken as JwtSecurityToken;
if (validJwt == null) { throw new ArgumentException("Invalid JWT"); }
if (principal != null)
{
context.User.AddIdentities(principal.Identities);
}
}
It only works for Azure AD. To support other ID providers (Facebook, Twitter, etc) you'd have to detect the relevant headers and figure out how to parse each provider's token. However, it should just be variations on the above theme.
You can give this library a try. I faced a similar problem and created this to simplify the use.
https://github.com/dasiths/NEasyAuthMiddleware
Azure App Service Authentication (EasyAuth) middleware for ASP.NET
CORE with fully customizable components with support for local
debugging
It hydrates the HttpContext.User by registering a custom authentication handler. To make things easier when running locally, it even has the ability to use a json file to load mocked claims.

is it a bug in mvc4 on cross domain authentication?

i used cross domain cookie authentication on mvc3 and .net4 it works fine and nice,
i create a another project on mvc4 and .net 4.5 and copy/past my codes from lower to uper version on mvc (i mean to mvc4),now my authentication cookie create on main domain but sub domain can not realize that user has been authenticated.
is it a bug in mvc4 or i must enable some futures or somethings like this?
my codes to create authentication cookie on main domain:
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1,
"fc5f06b006b44b05a257c406f4218638",//username
DateTime.Now,
DateTime.Now.AddDays(5),
true,
"members",
FormsAuthentication.FormsCookiePath);
// To give more security it is suggested to hash it
string hashCookies = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName,
hashCookies); // Hashed ticket
cookie.Expires = DateTime.Now.AddDays(5);
cookie.Domain = ".maindomain.com";
Response.Cookies.Add(cookie);
in sub domain use this line of codes to test user authentication:
var result = System.Web.HttpContext.Current.User.Identity.IsAuthenticated + "-" +
System.Web.HttpContext.Current.User.Identity.Name;
and in my global i have:
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
// look if any security information exists for this request
if (System.Web.HttpContext.Current.User != null)
{
// see if this user is authenticated, any authenticated cookie (ticket) exists for this user
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
// see if the authentication is done using FormsAuthentication
if (System.Web.HttpContext.Current.User.Identity is FormsIdentity)
{
// Get the roles stored for this request from the ticket
// get the identity of the user
FormsIdentity identity = (FormsIdentity)System.Web.HttpContext.Current.User.Identity;
// get the forms authetication ticket of the user
FormsAuthenticationTicket ticket = identity.Ticket;
// get the roles stored as UserData into the ticket
string[] roles = ticket.UserData.Split(',');
// create generic principal and assign it to the current request
System.Web.HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(identity, roles);
}
}
}
}
and in my web config :
<authentication mode="Forms">
<forms domain=".maindomain.com" name="atnc"
loginUrl="~/home" timeout="120" requireSSL="false" />
</authentication>