I work in a corporate and we have got an AAD for all the users in our organisation.
I am working on creating an app that authenticates users with AAD using OpenIdConnect. Now, I have followed this sample from Azure AD B2C. it works fine when I run it locally for the first time (after an hour break) and OnAuthorizationCodeReceived is triggered, however, when I stop and restart the app in visual studio OnAuthorizationCodeReceived doesn't get triggered and as a result, I get a null User (IAccount) when trying to retrieve token silently in this code block
public async Task<string> GetAccessToken(string scopes)
{
//var userClaims = User.Identity as System.Security.Claims.ClaimsIdentity;
var userClaims2 = ClaimsPrincipal.Current.Claims;
IConfidentialClientApplication cc = MsalAppBuilder.BuildConfidentialClientApplication();
var userAccount = await cc.GetAccountAsync(ClaimsPrincipal.Current.GetMsalAccountId());
var userAccount2 = await cc.GetAccountsAsync(ClaimsPrincipal.Current.FindFirst(Globals.ObjectIdClaimType).Value);
var userAccount3 = userAccount2.FirstOrDefault();
AuthenticationResult result = await cc.AcquireTokenSilent(new string[] { scopes }, userAccount3).ExecuteAsync();
return result.AccessToken;
}
My startup.Auth file looks like this:
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
// Configure OpenIDConnect, register callbacks for OpenIDConnect Notifications
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Sets the ClientId, authority, RedirectUri as obtained from web.config
ClientId = ConfigHelper.ClientId,
Authority = String.Format(CultureInfo.InvariantCulture, aadInstance, ConfigHelper.Tenant),
PostLogoutRedirectUri = ConfigHelper.PostLogoutRedirectUri,
RedirectUri = ConfigHelper.PostLogoutRedirectUri,
ResponseType = OpenIdConnectResponseTypes.CodeIdToken,
// ValidateIssuer set to false to allow work accounts from any organization to sign in to your application
// To only allow users from a single organizations, set ValidateIssuer to true and 'tenant' setting in web.config to the tenant name or Id (example: contoso.onmicrosoft.com)
// To allow users from only a list of specific organizations, set ValidateIssuer to true and use ValidIssuers parameter
TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
ValidateIssuer = true
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = (context) =>
{
//string redirectURI = string.Format("{0}://{1}{2}/", context.Request.Scheme, context.Request.Host, context.Request.PathBase);
string redirectURI = string.Format("https://{0}{1}/", context.Request.Host, context.Request.PathBase);
context.ProtocolMessage.RedirectUri = redirectURI;
context.ProtocolMessage.DomainHint = domain_hint;
return Task.FromResult(0);
},
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = (context) =>
{
if (ConfigHelper.NonceExceptionHandler && (context.Exception.Message.StartsWith("OICE_20004") || context.Exception.Message.Contains("IDX10311") || context.Exception.Message.Contains("IDX21323")))
{
context.SkipToNextMiddleware();
return Task.FromResult(0);
}
return Task.FromResult(0);
}
}
});
app.Use<MsOfficeLinkPrefetchMiddleware>();
// add this function into the app pipeline to Call my function OnAuth and call the next in the pipeline
app.Use((context, next) =>
{
// The function to call...
OnAuth(context);
return next.Invoke();
});
// limit the calls to the above function to be in the PostAuthenticate part of the stage
app.UseStageMarker(PipelineStage.PostAuthenticate);
}
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
context.HandleResponse();
context.Response.Redirect("/?errormessage=" + context.Exception.Message);
return Task.FromResult(0);
}
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
string redirectURI = string.Format("{0}://{1}{2}/", notification.Request.Scheme, notification.Request.Host, notification.Request.PathBase);
IConfidentialClientApplication confidentialClient = MsalAppBuilder.BuildConfidentialClientApplication(new ClaimsPrincipal(notification.AuthenticationTicket.Identity));
// Upon successful sign in, get & cache a token using MSAL
//user.readbasic.all
AuthenticationResult result = await confidentialClient.AcquireTokenByAuthorizationCode(new[] { "user.readbasic.all" }, notification.Code).ExecuteAsync();
}
I have been trying for a few days to solve this problem but to no avail. Please Help.
Cheers
Related
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app) {
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions() {
CookieDomain = ".xxx.com"
});
var notifications = new OpenIdConnectAuthenticationNotifications {
AuthenticationFailed = OnAuthenticationFailed
};
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions {
ClientId = SystemSettings.ClientId, //This is the client Id of the central Multi-tenant Azure AD application
Authority = SystemSettings.Authority,
PostLogoutRedirectUri = SystemSettings.PostLogoutRedirectUri,
Notifications = notifications,
//ProtocolValidator = new OpenIdConnectProtocolValidator() { RequireNonce = false},
UseTokenLifetime = false,
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters() {
ValidIssuers = SystemSettings.ValidIssuers
}
});
}
}
For SSO login we are calling OWIN context:
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = string.IsNullOrWhiteSpace(returnUrl) ? "/account/authenticated" : string.Format("/account/authenticated?companyCode={0}&returnUrl={1}", companyCode, HttpUtility.UrlEncode(returnUrl)) },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
return null;
After SSO succesfully login, I am redirecting to below route details:
[Route("account/authenticated")]
[AllowAnonymous]
public ActionResult Authenticated(string returnUrl, string companyCode) {
FileLogger.Log($"System.Web.HttpContext.Current.Request.IsAuthenticated: {System.Web.HttpContext.Current.Request.IsAuthenticated}");
var identity = (ClaimsIdentity)Thread.CurrentPrincipal.Identity;
var claims = JsonConvert.SerializeObject(identity?.Claims?.ToList(), new JsonSerializerSettings() {
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
FileLogger.Log($"claims: {claims}");
if (System.Web.HttpContext.Current.Request.IsAuthenticated) {
var token = AuthorizationService.AuthorizeUser();
FileLogger.Log($"AuthorizationService.AuthorizeUser() returns: {token}");
if (!string.IsNullOrWhiteSpace(token)) {
ViewBag.ClientCode = companyCode;
ViewBag.Token = token;
ViewBag.ReturnUrl = returnUrl;
return View();
}
return null;
}
var currentClaimsPrincipal = ClaimsPrincipal.Current;
if (currentClaimsPrincipal != null && currentClaimsPrincipal.Claims != null) {
var myClaimsPrincipal = new ClaimsIdentity(currentClaimsPrincipal.Claims);
}
return null;
}
But claim output is not coming and i am getting false authentication and no claim:
Identity:
{System.Security.Principal.GenericIdentity}
Actor: null
AuthenticationType: ""
BootstrapContext: null
Claims: {System.Security.Claims.ClaimsIdentity.<get_Claims>d__51}
CustomSerializationData: null
IsAuthenticated: false
Label: null
Name: ""
NameClaimType: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"
RoleClaimType: "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
As far as I knew, we can use the following code to get the claims after we complete Azure AD auth
var userClaims = User.Identity as System.Security.Claims.ClaimsIdentity;
/*
The token's claim "aud" is the application's client ID. For more deatils, please refer to https://learn.microsoft.com/en-us/azure/architecture/multitenant-identity/claims.
*/
foreach (var claim in userClaims.Claims) {
// get app id
}
// TenantId is the unique Tenant Id - which represents an organization in Azure AD
ViewBag.TenantId = userClaims?.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid")?.Value;
I solved it by updating OWIN packages and with below links:
https://dotnetcodetips.com/Tip/91/Azure-OWIN-website-login-gets-stuck-on-a-never-ending-redirect-loop.
I'm trying to add Token Authentication with JWT to my .Net Core 2.0 app. I have a simple controller that returns a list of users for testing.
[Authorize]
[Route("api/[controller]")]
public class UsersController : Controller
{
...
[HttpGet]
[Route("api/Users/GetUsers")]
public IEnumerable<ApplicationUser> GetUsers()
{
return _userManager.Users;
}
}
I have an API Controller for Token security. It has a login method which returns a Token string result.
[HttpPost(nameof(Login))]
public async Task<IActionResult> Login([FromBody] LoginResource resource)
{
if (resource == null)
return BadRequest("Login resource must be asssigned");
var user = await _userManager.FindByEmailAsync(resource.Email);
if (user == null || (!(await _signInManager.PasswordSignInAsync(user, resource.Password, false, false)).Succeeded))
return BadRequest("Invalid credentials");
string result = GenerateToken(user.UserName, resource.Email);
// Token is created, we can sign out
await _signInManager.SignOutAsync();
return Ok(result);
}
private string GenerateToken(string username, string email)
{
var claims = new Claim[]
{
new Claim(ClaimTypes.Name, username),
new Claim(ClaimTypes.Email, email),
new Claim(JwtRegisteredClaimNames.Nbf, new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds().ToString()),
new Claim(JwtRegisteredClaimNames.Exp, new DateTimeOffset(DateTime.Now.AddDays(1)).ToUnixTimeSeconds().ToString()),
};
var token = new JwtSecurityToken(
new JwtHeader(new SigningCredentials(
new SymmetricSecurityKey(Encoding.UTF8.GetBytes("the secret that needs to be at least 16 characeters long for HmacSha256")),
SecurityAlgorithms.HmacSha256)),
new JwtPayload(claims));
return new JwtSecurityTokenHandler().WriteToken(token);
}
I have a small console app just for testing the API. When I attempt to Get the Users using the jwt. I receive an immediate "unauthorized". If I remove the "[Authorize]" from the users Controller... success. It appears that my header Authorization is not recognized, but not sure why.
private static async Task<String> GetUsers(String jwt)
{
var url = "https://localhost:44300/";
var apiUrl = $"/api/Users/";
using (var client = new HttpClient() { BaseAddress = new Uri(url) })
{
client.BaseAddress = new Uri(url);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {jwt}");
using (var response = await client.GetAsync(apiUrl))
{
if (response.StatusCode == System.Net.HttpStatusCode.OK)
return await response.Content.ReadAsStringAsync();
else return null;
}
}
}
I'm basing my attempts on the article here ... some of which might be slightly out of date.
Update - Excerpt of Startup.cs
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "Jwt";
options.DefaultChallengeScheme = "Jwt";
}).AddJwtBearer("Jwt", options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
//ValidAudience = "the audience you want to validate",
ValidateIssuer = false,
//ValidIssuer = "the isser you want to validate",
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("the secret that needs to be at least 16 characeters long for HmacSha256")),
ValidateLifetime = true, //validate the expiration and not before values in the token
ClockSkew = TimeSpan.FromMinutes(5) //5 minute tolerance for the expiration date
};
});
Configure...
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
//app.UseJwtBearerAuthentication(
// new Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerOptions
// );
}
Solution:
This line was escaping the token therefore causing it to be invalid when passed in the next request:
var result = await response.Content.ReadAsStringAsync();
Replaced with:
var result = await response.Content.ReadAsAsync<string>();
Note: To use this ext method I had to "install-package Microsoft.AspNet.WebApi.Client"
I used JWT authentication in my one of project. I would like to show my implementation, maybe this will help you. But probably you forget to add UseAuthentication(); into configure method in startup class.
startup.cs
public void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
app.UseMvc();
}
public void ConfigureServices(IServiceCollection services)
{
var appSettings = Configuration.GetSection("AppSettings");
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}
)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = true,
ValidAudience = appSettings["JwtAudience"],
ValidateIssuer = true,
ValidIssuer = appSettings["JwtIssuer"],
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(appSettings["JwtSigningKey"]))
};
});
}
generateToken method
private string GenerateToken(string email)
{
SecurityKey securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_appSettings.Value.JwtSigningKey));
var token = new JwtSecurityToken(
issuer: _appSettings.Value.JwtIssuer,
audience: _appSettings.Value.JwtAudience,
claims: new[]
{
new Claim(JwtRegisteredClaimNames.UniqueName, email),
new Claim(JwtRegisteredClaimNames.Email, email),
new Claim(JwtRegisteredClaimNames.NameId, Guid.NewGuid().ToString())
},
expires: DateTime.Now.AddMinutes(_appSettings.Value.JwtExpireMinute),
signingCredentials: new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256)
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
I had created a nuget package NetCore.Jwt to simplify this process recently. I didn't find it worth writing all the code each time you needed a Jwt, when you can handle cookies simply with the function SignInAsync. However, if you prefer the manual way, Celal's answer is a clear and straightforward guide for this process.
Alternatively, you can install NetCore.Jwt and use the following in your startup:
services.AddAuthentication(NetCoreJwtDefaults.SchemeName)
.AddNetCoreJwt(options =>
{
// configure your token options such as secret, expiry, and issuer here
});
In your Login function, you can use the extension function for HttpContext
string token = HttpContext.GenerateBearerToken( new Claim[]
{
new Claim(ClaimTypes.Name, username),
new Claim(ClaimTypes.Email, email),
new Claim(JwtRegisteredClaimNames.Nbf, new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds().ToString()),
new Claim(JwtRegisteredClaimNames.Exp, new DateTimeOffset(DateTime.Now.AddDays(1)).ToUnixTimeSeconds().ToString()),
});
In your program.cs dont forget to have this code (and in order) :
app.UseAuthentication();
app.UseAuthorization();
I have a web api / mvc hybrid app and I have configured it to use cookie authentication. This works fine for the mvc portion of the application. The web api does enforce the authorization, but instead of returning a 401 - Unauthorised it returns a 302 - Found and redirects to the login page. I would rather it returns a 401. I have attempted to hook into the CookieAuthenticationProvider.OnApplyRedirect delegate, but this doesn't seem to be called. What have I missed? My current setup is below:
AntiForgeryConfig.UniqueClaimTypeIdentifier = Constants.ClaimTypes.Subject;
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
ExpireTimeSpan = TimeSpan.FromMinutes(20),
SlidingExpiration = true,
CookieHttpOnly = true,
CookieSecure = CookieSecureOption.Never, //local non ssl-dev only
Provider = new CookieAuthenticationProvider
{
OnApplyRedirect = ctx =>
{
if (!IsAjaxRequest(ctx.Request))
{
ctx.Response.Redirect(ctx.RedirectUri);
}
}
}
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = IdentityConfig.Authority,
ClientId = IdentityConfig.SoftwareClientId,
Scope = "openid profile roles",
RedirectUri = IdentityConfig.RedirectUri,
ResponseType = "id_token",
SignInAsAuthenticationType = "Cookies"
});
In your example the UseCookieAuthentication no longer controls this, instead the UseOpenIdConnectAuthentication does. This involves using the Notifications property and intercepting OpenID Connect authentication requests.
Try out the following for inspiration:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = IdentityConfig.Authority,
ClientId = IdentityConfig.SoftwareClientId,
Scope = "openid profile roles",
RedirectUri = IdentityConfig.RedirectUri,
ResponseType = "id_token",
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = notification =>
{
if (notification.ProtocolMessage.RequestType == OpenIdConnectRequestType.AuthenticationRequest)
{
if (IsAjaxRequest(notification.Request) && notification.Response.StatusCode == (int)HttpStatusCode.Unauthorized)
{
notification.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
notification.HandleResponse();
return Task.FromResult(0);
}
}
return Task.FromResult(0);
}
}
});
In my case the IsAjaxRequest did not do the trick. Instead I rely on all routes to the WebAPI being under "/api", so instead of the IsAjaxRequest I do:
RedirectToIdentityProvider = context => {
if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication){
if (context.Request.Path.StartsWithSegments(new PathString("/api")) && context.Response.StatusCode == (int)HttpStatusCode.Unauthorized){
context.HandleResponse();
return Task.CompletedTask;
}
}
return Task.CompletedTask;
}
I am running this sample to create multitenant web app that connects using AzureAD with Owin OpenIDConnect middleware. The issued .AspNet.Cookies to authenticate between "my client" and "my server" is always a Session cookie. I would like to set it a Max-Age or an expiration date instead. I have tried several correction without success, for example, I tried to change the the ExpireTimeSpan (see code below) but in my browser cookie inspector I still see Expiration/ Max-Age: Session.
Also, why the SignOut method uses openidconnect and cookies for authentication types while the SignIn method only openidconnect?
AccountController
public void SignIn()
{
HttpContext.GetOwinContext()
.Authentication.Challenge(new AuthenticationProperties {RedirectUri = SettingsHelper.LoginRedirectRelativeUri},
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
public void SignOut()
{
HttpContext.GetOwinContext().Authentication.SignOut(
new AuthenticationProperties { RedirectUri = SettingsHelper.LogoutRedirectRelativeUri, },
OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
}
And in Start.Auth.cs
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
ExpireTimeSpan = TimeSpan.FromHours(1),
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
AuthenticationType = OpenIdConnectAuthenticationDefaults.AuthenticationType,
ClientId = SettingsHelper.ClientId,
Authority = SettingsHelper.Authority,
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = false
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
// If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away.
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
ClientCredential credential = new ClientCredential(SettingsHelper.ClientId, SettingsHelper.AppKey);
string tenantID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
string signInUserId = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
AuthenticationContext authContext = new AuthenticationContext(string.Format("{0}/{1}", SettingsHelper.AuthorizationUri, tenantID), new ADALTokenCache(signInUserId));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, SettingsHelper.AADGraphResourceId);
return Task.FromResult(0);
},
RedirectToIdentityProvider = (context) =>
{
string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase;
context.ProtocolMessage.RedirectUri = appBaseUrl + SettingsHelper.LoginRedirectRelativeUri;
context.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl + SettingsHelper.LogoutRedirectRelativeUri;
return Task.FromResult(0);
},
AuthenticationFailed = (context) =>
{
context.HandleResponse();
return Task.FromResult(0);
}
}
});
}
I'm trying to implement following functionality:
User signs in into Live Id account from Windows Phone 8.1 (or Universal) app.
App accesses Web Api that I develop with ASP.NET Web Api 2
In this Web Api I need to authenticate the user.
Later, I want to authenticate same user in web app
Here is what I'm doing, and it doesn't work.
In my Windows Phone App:
var authClient = new LiveAuthClient("http://myservice.cloudapp.net");
LiveLoginResult result = await authClient.LoginAsync(new string[] { "wl.signin" });
if (result.Status == LiveConnectSessionStatus.Connected)
{
connected = true;
var identity = await ConnectToApi(result.Session.AuthenticationToken);
Debug.WriteLine(identity);
}
And then
private async Task<string> ConnectToApi(string token)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://myservice.cloudapp.net/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
// HTTP GET
HttpResponseMessage response = await client.GetAsync("api/values");
if (response.IsSuccessStatusCode)
{
string result = await response.Content.ReadAsStringAsync();
return result;
}
else
return response.ReasonPhrase;
}
}
And then in my web api I have following
public void ConfigureAuth(IAppBuilder app)
{
app.UseMicrosoftAccountAuthentication(
clientId: "my client id",
clientSecret: "my secret");
}
I registered http://myservice.cloudapp.net as redirect url.
The problem is authentication doesn't work, web api actions do not recognize the user.
I got it totally wrong. First, I actually need to use app.UseJwtBearerAuthentication method. The example was found here http://code.lawrab.com/2014/01/securing-webapi-with-live-id.html. But when I tried, I got this error in the output
IDX10500: Signature validation failed. Unable to resolve SecurityKeyIdentifier: 'SecurityKeyIdentifier
(
IsReadOnly = False,
Count = 1,
Clause[0] = System.IdentityModel.Tokens.NamedKeySecurityKeyIdentifierClause
)
This one took me a while to figure out, until I found this post: JwtSecurityTokenHandler 4.0.0 Breaking Changes?
Putting these things together, I got the solution that seems to work now in my testing environment:
public void ConfigureAuth(IAppBuilder app)
{
var sha256 = new SHA256Managed();
var sKey = "<Secret key>" + "JWTSig";
var secretBytes = new UTF8Encoding(true, true).GetBytes(sKey);
var signingKey = sha256.ComputeHash(secretBytes);
var securityKeyProvider = new SymmetricKeyIssuerSecurityTokenProvider("urn:windows:liveid", signingKey);
var securityKey = securityKeyProvider.SecurityTokens.First().SecurityKeys.First();
var jwtOptions = new JwtBearerAuthenticationOptions()
{
//AllowedAudiences = new[] { "<url>" },
//IssuerSecurityTokenProviders = new[]
//{
// new SymmetricKeyIssuerSecurityTokenProvider("urn:windows:liveid",signingKey)
//},
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters()
{
IssuerSigningKeyResolver = (token, securityToken, keyIdentifier, validationParameters) =>
{
return securityKey;
},
ValidAudience = "<url>",
ValidIssuer = securityKeyProvider.Issuer
}
};
app.UseJwtBearerAuthentication(jwtOptions);
}
For anybody looking to do this from JavaScript I managed to get this working by following steps from this blog. You can find the audience by putting your token through jwt.io
https://blog.dirk-eisenberg.de/2014/08/30/validate-authentication_token-from-microsoft-liveid-with-node-express-jwt/
const validateLiveJWT = (token) => {
const secret = '<<SECRET>>';
const sha256 = crypto.createHash('sha256');
sha256.update(secret + 'JWTSig', 'utf8');
const secretBase64 = sha256.digest('base64');
const secret = new Buffer(secretBase64, 'base64');
const options = {
audience: '<<AUDIENCE>>',
issuer: 'urn:windows:liveid',
};
return new Promise((resolve) => {
jwt.verify(token, secret, options, (err: any, claims: any) => {
if (err) {
resolve(undefined);
} else {
resolve(claims);
}
});
});
}