ASP.NET MVC OpenIdConnect OnAuthorizationCodeReceived not triggering after app restart - asp.net

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

Azure SSO login with ApplicationId and Tenant Id not returning successful claim output

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.

Core 2.0 API Auth with JWT returns unauthorized

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

Identity Server 3 - 401 on Ajax Calls instead of 302

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;
}

Set Expires or Max-Age for .AspNet.Cookies with Owin OpenIdConnect Middleware

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

Login from Universal App to Web Api using Live Id

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

Resources