I am calling Async method in a non-async method in the below way in WebApi authentication handler. And it hangs when it executes the inner external Async method. I need to set the claims before the subsequent code executes. So I am setting the return value to the Thread.CurrentPrincipal. Please advise.
I have tried the below and none of them worked.
Task.Run(() => Thread.CurrentPrincipal = this.ValidateTokenAsync(accessToken).GetAwaiter().GetResult()); - this works but the subsequent code execution does not wait on this and so the claims are not utilized there.
Thread.CurrentPrincipal = this.ValidateTokenAsync(accessToken).Result;
Task<ClaimsPrincipal> claimsPrincipalTask = this.ValidateTokenAsync(accessToken);
Task.WaitAll(claimsPrincipalTask);
Thread.CurrentPrincipal = Thread.CurrentPrincipal.GetAwaiter().GetResult();
Thread.CurrentPrincipal = this.ValidateTokenAsync(accessToken).GetAwaiter().GetResult();
private async Task<ClaimsPrincipal> ValidateTokenAsync(string accessToken)
{
LoggingUtilities.Logger.TraceInformation("Validating JWT.");
ClaimsPrincipal principal = ClaimsPrincipal.Current;
if (principal == null || principal.Identity == null || !principal.Identity.IsAuthenticated)
{
principal = await JwtValidator.ValidateTokenAsync(accessToken).ConfigureAwait(false);
}
return principal;
}
The JwtValidator.ValidateTokenAsync method:
public static async Task<ClaimsPrincipal> ValidateTokenAsync(string accessToken)
{
string authority = string.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
string issuer = null;
string stsDiscoveryEndpoint = string.Format(CultureInfo.InvariantCulture, "{0}/.well-known/openid-configuration", authority);
List<SecurityToken> signingTokens = null;
try
{
// The issuer and signingTokens are cached for 24 hours. They are updated if any of the conditions in the if condition is true.
if (DateTime.UtcNow.Subtract(stsMetadataRetrievalTime).TotalHours > 24
|| string.IsNullOrEmpty(globalIssuer)
|| globalSigningTokens == null)
{
// Get tenant information that's used to validate incoming jwt tokens
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint);
OpenIdConnectConfiguration config = await configManager.GetConfigurationAsync().ConfigureAwait(false);
globalIssuer = config.Issuer;
globalSigningTokens = config.SigningTokens.ToList();
stsMetadataRetrievalTime = DateTime.UtcNow;
}
issuer = globalIssuer;
signingTokens = globalSigningTokens;
}
catch (Exception)
{
LoggingUtilities.Logger.TraceWarning("Failed to get signing tokens.");
throw;
}
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
TokenValidationParameters validationParameters = new TokenValidationParameters
{
ValidAudience = audience,
ValidIssuer = issuer,
IssuerSigningTokens = signingTokens,
CertificateValidator = X509CertificateValidator.None
};
ClaimsPrincipal principal;
try
{
// Validate token.
SecurityToken validatedToken;
principal = tokenHandler.ValidateToken(
accessToken,
validationParameters,
out validatedToken);
}
catch (SecurityTokenValidationException)
{
LoggingUtilities.Logger.TraceWarning("Failed to validate the JWT.");
throw;
}
catch (Exception)
{
LoggingUtilities.Logger.TraceWarning("Failed to validate the JWT.");
throw;
}
return principal;
}
Related
I am trying to write unit tests for authentication logic implemented using Azure AD client credentials flow using MOQ.
The first test case is to check that if the "Audience" is valid. I am trying to mock claim to set up the "aud" or "appId" claims using ClaimTypes but not able to find anything like ClaimTypes.Aud
var identity = new ClaimsIdentity(new Claim[] {
new Claim(ClaimTypes.Name, "Sahil")
});
var mockPrincipal = new Mock<ClaimsPrincipal>(identity);
mockPrincipal.Setup(x => x.Identity).Returns(identity);
mockPrincipal.Setup(x => x.IsInRole(It.IsAny<string>())).Returns(true);
How can I set up the "aud" and "appId" claims in C#
OR
Just setup mockPrincipal so that when it tries to check if "aud" is valid it returs false.
I am trying to write unit tests for the below code.
public void Authenticate(JwtBearerOptions options)
{
_configuration.Bind("AzureAD", options);
options.TokenValidationParameters.ValidateAudience = true;
options.TokenValidationParameters.ValidateIssuerSigningKey = true;
options.TokenValidationParameters.ValidateIssuer = true;
options.Events ??= new JwtBearerEvents();
var existingHandlers = options.Events.OnTokenValidated;
options.Events.OnTokenValidated = async context =>
{
string appId = GetAppIdFromToken(context);
bool isAllowed = await CheckAppIdIsAllowedAsync(context, appId);
if (isAllowed)
{
_logger.LogInformation($"[{nameof(Authenticate)}] AppId in allow list");
}
else
{
_logger.LogError($"[{nameof(Authenticate)}] AppId {appId} not in allowed list");
}
await Task.CompletedTask.ConfigureAwait(false);
};
options.Events.OnTokenValidated += existingHandlers;
}
private string GetAppIdFromToken(TokenValidatedContext context)
{
string appId = context.Principal.Claims.FirstOrDefault(x => x.Type == "appid" || x.Type == "azp")?.Value;
return appId;
}
private async Task<bool> CheckAppIdIsAllowedAsync(TokenValidatedContext context, string appId)
{
IEnumerable<string> AllowedApps = _configuration.GetSection("AllowedAppPrincipals").Get<string[]>();
var FoundAppId = AllowedApps.FirstOrDefault(a => a == appId);
if (FoundAppId == null)
{
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
context.Response.ContentType = "application/json";
const string message = "{\"error\" : \"Unacceptable app principal\"}";
byte[] arr = Encoding.ASCII.GetBytes(message);
await context.Response.BodyWriter.WriteAsync(arr);
context.Fail(message);
return false;
}
return true;
}
How to mock aud and appId claims with Moq?
I tried to reproduce how to get audience invalid in my environment.
It usually happens when the issuer endpoint is different or scope is not given correctly or different scope than what is intended is mentioned.
https://login.microsoftonline.com/xx/oauth2/v2.0/token
Here i gave scope for api other than microsoft graph.
But next tep i am calling graph endpoint and so i am getting invalid audience error .
https://graph.microsoft.com/v1.0/users/xxx
To check for mocking test , you can consider below point as direct claims for "aud" audience in c# couldn't be obtained AFAIK.
Aud claim is Application ID URI or GUID Identifies the intended
audience of the token. In v2.0 tokens, audience must be client ID
of the API whereas in v1.0 tokens, it can be the client ID or the
resource URI used in the request.
One way to validate it is to check following way with issuer/audience
You can give custom values according to the authorization endpoint
Code:
string AUDIENCE = "<GUID of your Audience according to the app>";
string TENANT = "<GUID of your Tenant>";
private static async Task<SecurityToken> validateJwtTokenAsync(string token)
{
// URL based on your AAD-TenantId
var stsDiscoveryEndpoint = String.Format(CultureInfo.InvariantCulture, "https://login.microsoftonline.com/{0}/.well-known/openid-configuration", TENANT);
//To Get tenant information
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint)
// Get Config from AAD:
var config = await configManager.GetConfigurationAsync();
// Validate token:
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidAudience = AUDIENCE,
ValidIssuer = config.Issuer,
IssuerSigningTokens = config.SigningTokens,
CertificateValidator = X509CertificateValidator.ChainTrust,
};
var validatedToken = (SecurityToken)new JwtSecurityToken();
tokenHandler.ValidateToken(token, validationParameters, out validatedToken);
return validatedToken;
}
Or
var TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = key,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = false,
....
ValidIssuer = configuration["JwtAuthentication:Issuer"],
ValidAudience = configuration["JwtAuthentication:Audience"]
};
So from validation parameters, you can get if that we that audience was valid or not, it majorly occurs when issuer is different from what we expected or when scopes are not correct.
You can try get those validate as claims to check for mocking
Snippent below taken from TokenValidationParameters.AudienceValidator, System.IdentityModel.Tokens C# (CSharp) Code Examples - HotExamples
public virtual ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken
validatedToken)
{
if (string.IsNullOrWhiteSpace(securityToken))
{
throw new ArgumentNullException("securityToken");
}
if (validationParameters == null)
{
throw new ArgumentNullException("validationParameters");
}
if (validationParameters.ValidateAudience)
{
if (validationParameters.AudienceValidator != null)
{
if
(!validationParameters.AudienceValidator(jwt.Audiences, jwt,
validationParameters))
{
throw new SecurityTokenInvalidAudienceException(string.Format(CultureInfo.InvariantCulture,
ErrorMessages.IDX10231, jwt.ToString()));
}
}
else
{
this.ValidateAudience(jwt.Audiences, jwt,
validationParameters);
}
}
ClaimsIdentity identity = this.CreateClaimsIdentity(jwt, issuer,
validationParameters);
if (validationParameters.SaveSigninToken)
{
identity.BootstrapContext = new
BootstrapContext(securityToken);
}
validatedToken = jwt;
return new ClaimsPrincipal(identity);
}
Also check Reference :c# - How to mock ConfigurationManager.AppSettings with moq - Stack Overflow
I am create a JWT access token and refresh token on login of valid user, access token is short lived and refresh token is with expiration time of 7 days, When I am trying to generate new access token after expiry using refresh token it is working fine and response with new access token and refresh token but after long time such as after 3 or 4 hours when I am trying it is not working. I am also comment in Refresh token method code where I am getting error.
Please see my code:
Controller:
public IActionResult RefreshToken([FromBody] RefreshTokenRequest request)
{
try
{
if (string.IsNullOrWhiteSpace(request.RefreshToken))
{
return Unauthorized();
}
var jwtResult = _jwtAuthManager.Refresh(request.RefreshToken, request.AccessToken, DateTime.Now);
var userName = jwtResult.RefreshToken.UserName;
var role = _userService.GetUserRole(userName);
var claims = new[]
{
new Claim(ClaimTypes.Role, role)
};
_logger.LogInformation($"User [{userName}] has refreshed JWT Token");
if (jwtResult == null)
{
return BadRequest();
}
return Ok(new
{
UserName = userName,
Role= role,
AccessToken = jwtResult.AccessToken,
RefreshToken = jwtResult.RefreshToken.TokenString,
Status = "Success",
Message = "New access token generated successfully"
});
}
catch (SecurityTokenException e)
{
return Unauthorized(e.Message); // return 401 so that the client side can redirect the user to login page
}
}
Generate token method:
public JwtAuthResult GenerateTokens(string username, Claim[] claims, DateTime now)
{
var shouldAddAudienceClaim = string.IsNullOrWhiteSpace(claims?.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Aud)?.Value);
var jwtToken = new JwtSecurityToken(
_jwtTokenConfig.Issuer,
shouldAddAudienceClaim ? _jwtTokenConfig.Audience : string.Empty,
claims,
expires: now.AddMinutes(_jwtTokenConfig.AccessTokenExpiration),
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(_secret), SecurityAlgorithms.HmacSha256Signature));
var accessToken = new JwtSecurityTokenHandler().WriteToken(jwtToken);
var refreshToken = new RefreshToken
{
UserName = username,
TokenString = GenerateRefreshTokenString(),
ExpireAt = now.AddMinutes(_jwtTokenConfig.RefreshTokenExpiration),
};
_usersRefreshTokens.AddOrUpdate(refreshToken.TokenString, refreshToken, (s, t) => refreshToken);
return new JwtAuthResult
{
AccessToken = accessToken,
RefreshToken = refreshToken
};
}
Refresh Token Method:
public JwtAuthResult Refresh(string refreshToken, string accessToken, DateTime now)
{
var (principal, jwtToken) = DecodeJwtToken(accessToken);
if (jwtToken == null || !jwtToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256Signature))
{
throw new SecurityTokenException("Invalid token");
}
var userName = principal.Identity?.Name;
if (!_usersRefreshTokens.TryGetValue(refreshToken, out var existingRefreshToken))
{
throw new SecurityTokenException("Invalid token not found");
}
var result = existingRefreshToken;
if (existingRefreshToken.UserName != userName || existingRefreshToken.ExpireAt <= now) //After 3 or 4 hours I am getting error in this condition.
{
throw new SecurityTokenException("Invalid UserName or refresh token expired");
}
return GenerateTokens(userName, principal.Claims.ToArray(), now); // need to recover the original claims
}
Claim Principal Method:
public (ClaimsPrincipal, JwtSecurityToken) DecodeJwtToken(string token)
{
if (string.IsNullOrWhiteSpace(token))
{
throw new SecurityTokenException("Invalid token");
}
var principal = new JwtSecurityTokenHandler()
.ValidateToken(token,
new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = _jwtTokenConfig.Issuer,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(_secret),
ValidAudience = _jwtTokenConfig.Audience,
ValidateAudience = true,
ValidateLifetime = false,
ClockSkew = TimeSpan.FromMinutes(1)
},
out var validatedToken);
return (principal, validatedToken as JwtSecurityToken);
}
I tried this tutorial because I want to use the Microsoft Graph API to create massive teams in Microsoft Teams.
The only difference with the tutorial is that I used the next choice in Authentication section of Azure AD admin center:
"Accounts in this organizational directory only (myuniversity only - Single tenant)"
Because of this I changed my code to use endpoint for single tenant
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = appId,
//Authority = "https://login.microsoftonline.com/common/v2.0",//
Authority = "https://login.microsoftonline.com/{tenantid}/v2.0",
Scope = $"openid email profile offline_access {graphScopes}",
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
TokenValidationParameters = new TokenValidationParameters
{
// For demo purposes only, see below
ValidateIssuer = false
// In a real multi-tenant app, you would add logic to determine whether the
// issuer was from an authorized tenant
//ValidateIssuer = true,
//IssuerValidator = (issuer, token, tvp) =>
//{
// if (MyCustomTenantValidation(issuer))
// {
// return issuer;
// }
// else
// {
// throw new SecurityTokenInvalidIssuerException("Invalid issuer");
// }
//}
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailedAsync,
AuthorizationCodeReceived = OnAuthorizationCodeReceivedAsync
}
}
);
}
After authentication of the user the code I run to OnAuthorizationCodeReceivedAsync method but got an exception in AcquireTokenByAuthorizationCode method
Here is the method
private async Task OnAuthorizationCodeReceivedAsync(AuthorizationCodeReceivedNotification notification)
{
var idClient = ConfidentialClientApplicationBuilder.Create(appId)
.WithRedirectUri(redirectUri)
.WithClientSecret(appSecret)
.Build();
var accounts = await idClient.GetAccountsAsync();
string message;
string debug;
try
{
string[] scopes = graphScopes.Split(' ');
var result = await idClient.AcquireTokenByAuthorizationCode(scopes, notification.Code).ExecuteAsync();
message = "Access token retrieved.";
debug = result.AccessToken;
}
catch (MsalException ex)
{
message = "AcquireTokenByAuthorizationCodeAsync threw an exception";
debug = ex.Message;
}
var queryString = $"message={message}&debug={debug}";
if (queryString.Length > 2048)
{
queryString = queryString.Substring(0, 2040) + "...";
}
notification.HandleResponse();
notification.Response.Redirect($"/Home/Error?{queryString}");
}
The exception is:
AcquireTokenByAuthorizationCodeAsync threw an exception
AADSTS50194: Application 'application id'(ASP.NET Graph Tutorial) is
not configured as a multi-tenant application. Usage of the /common
endpoint is not supported for such applications created after
'10/15/2018'. Use a tenant-specific endpoint or configure the
application to be multi-tenant. Trace ID:
5f0fbf2e-5d63-40d4-a833-ca8627a02d00
Correlation ID: 3ec4ec7b-0c86-4e2b-a053-9823f977499d Timestamp:
2021-02-16 20:21:03Z
I want to use single-tenant authentication for my organization only
If you want to require AD token from one specific tenant with MSAL.NET, you can tell the SDK from which tenant to obtain the token by mentioning the specific Authority. For more details, please refer to here.
For example
private static string appId = ConfigurationManager.AppSettings["ida:AppId"];
private static string appSecret = ConfigurationManager.AppSettings["ida:AppSecret"];
private static string redirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
private static string graphScopes = ConfigurationManager.AppSettings["ida:AppScopes"];
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = appId,
Authority = "https://login.microsoftonline.com/<your tenant id>/v2.0",
Scope = $"openid email profile offline_access {graphScopes}",
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
TokenValidationParameters = new TokenValidationParameters
{
// For demo purposes only, see below
ValidateIssuer = false
// In a real multi-tenant app, you would add logic to determine whether the
// issuer was from an authorized tenant
//ValidateIssuer = true,
//IssuerValidator = (issuer, token, tvp) =>
//{
// if (MyCustomTenantValidation(issuer))
// {
// return issuer;
// }
// else
// {
// throw new SecurityTokenInvalidIssuerException("Invalid issuer");
// }
//}
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailedAsync,
AuthorizationCodeReceived = OnAuthorizationCodeReceivedAsync
}
}
);
}
private async Task OnAuthorizationCodeReceivedAsync(AuthorizationCodeReceivedNotification notification)
{
var idClient = ConfidentialClientApplicationBuilder.Create(appId)
.WithRedirectUri(redirectUri)
.WithClientSecret(appSecret)
.WithAuthority("https://login.microsoftonline.com/<your tenant id>")
.Build();
string message;
string debug;
try
{
string[] scopes = graphScopes.Split(' ');
var result = await idClient.AcquireTokenByAuthorizationCode(
scopes, notification.Code).ExecuteAsync();
message = "Access token retrieved.";
debug = result.AccessToken;
}
catch (MsalException ex)
{
message = "AcquireTokenByAuthorizationCodeAsync threw an exception";
debug = ex.Message;
}
var queryString = $"message={message}&debug={debug}";
if (queryString.Length > 2048)
{
queryString = queryString.Substring(0, 2040) + "...";
}
notification.HandleResponse();
notification.Response.Redirect($"/Home/Error?{queryString}");
}
private Task OnAuthenticationFailedAsync(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
notification.HandleResponse();
string redirect = $"/Home/Error?message={notification.Exception.Message}";
if (notification.ProtocolMessage != null && !string.IsNullOrEmpty(notification.ProtocolMessage.ErrorDescription))
{
redirect += $"&debug={notification.ProtocolMessage.ErrorDescription}";
}
notification.Response.Redirect(redirect);
return Task.FromResult(0);
}
I have an issue in the Try Catch block of my code below.
In function SetUser, I use the getId function that returns an Id if the user exists in DB otherwise, I get a NullReferenceException.
I call this function in the try catch block in Login. I have a problem with the catch because when the exception is generated, I would like the user to be redirected to the register page. But when I try to execute my code with a non-existing user, I think that I have a kind of infinite loop because my page doesn't stop loading. I don't understand what I'm doing wrong. Need help please
function Login:
public static void Login(HttpRequest Request, HttpResponse Response, string redirectUri)
{
if (Request.IsAuthenticated)
return;
if (!Request.Form.AllKeys.Contains("id_token"))
return;
string value = Request.Form.Get("id_token");
JObject id_token = JwtDecode(value);
string upn = id_token.GetValue("upn").ToString();
DateTime expiretime = GetExpireTime(id_token);
try
{
SetUser(id_token);
}
catch (Exception ex)
{
Response.Redirect("~/register.aspx");
}
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, upn, DateTime.UtcNow, expiretime, false, id_token.ToString(), FormsAuthentication.FormsCookiePath);
string encryptedcookie = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedcookie);
cookie.Expires = expiretime;
Response.Cookies.Add(cookie);
redirectUri = GetRedirectUrl(Request, redirectUri);
Response.Redirect(redirectUri, true);
}
function setUser:
private static void SetUser(JObject id_token)
{
string email = id_token.GetValue("unique_name").ToString();
string name = id_token.GetValue("given_name").ToString();
DataSet ds;
List<Claim> claims = new List<Claim>()
{
new Claim(ClaimTypes.Email, email),
new Claim(ClaimTypes.Name, GetId(email))
};
string roles= "SELECT name FROM AspNetRoles;
ds = GetDataSet(roles);
if (ds.Tables.Count > 0)
{
foreach (var row in ds.Tables(0).Rows)
claims.Add(new Claim(ClaimTypes.Role, row("name")));
}
ClaimsIdentity claimsIdentity = new ClaimsIdentity(claims, "Cookies");
ClaimsPrincipal principal = new ClaimsPrincipal(claimsIdentity);
HttpContext.Current.GetOwinContext().Authentication.User = principal;
Thread.CurrentPrincipal = principal;
}
function getId:
public static string getId(string email)
{
return ((new UserManager()).FindByEmail(email)).Id;
}
I can't read token claims from Bearer JWT token. Login is working, the http request comes with a valid jwt token to the backend.
The application is self hosted on IIS7.
Here is my code on server side:
SecurityConfig.cs
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(24),
Provider = new AuthorizationServerProvider() ,
AccessTokenFormat = new JwtFormat(TimeSpan.FromHours(24))
});
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
AuthorizationServerProvider.cs
ClaimsIdentity id = new ClaimsIdentity(context.Options.AuthenticationType);
id.AddClaim(new Claim(InosysClaimTypes.UserId, Convert.ToString(appContext.UserId)));
id.AddClaim(new Claim(InosysClaimTypes.Username, context.UserName));
id.AddClaim(new Claim(InosysClaimTypes.Password, context.Password));
id.AddClaim(new Claim(InosysClaimTypes.FirNr, Convert.ToString(appContext.FirmenNummer)));
id.AddClaim(new Claim(InosysClaimTypes.FirNdl, Convert.ToString(appContext.Niederlassung)));
id.AddClaim(new Claim(InosysClaimTypes.Bereich, Convert.ToString(appContext.Bereich)));
id.AddClaim(new Claim(InosysClaimTypes.Sprache, Convert.ToString(appContext.Sprache)));
id.AddClaim(new Claim(InosysClaimTypes.SchiffNummern, appContext.SchiffNummern == null ? "" : string.Join(",", appContext.SchiffNummern)));
id.AddClaim(new Claim(InosysClaimTypes.Geschaeftsjahr, Convert.ToString(appContext.Geschaeftsjahr)));
var principal = new ClaimsPrincipal(id);
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
context.Validated(id);
In the ApiController i try to get the caller's payload information like this:
ClaimsIdentity identity = User.Identity as ClaimsIdentity;
if (identity != null)
{
appContext.UserId = Convert.ToInt32(identity.FindFirst(InosysClaimTypes.UserId).Value);
appContext.Username = identity.FindFirst(InosysClaimTypes.Username).Value;
}
That is the identity variable debugged:
identity
I don't know what is going wrong with your AuthorizationServerProvider.cs ,
But from the moment you provide a jwt token in your request header i think it will work this way.
I process the Header with an AuthorizeAttribute on each Controller accepting JWT authorization to set the Current Principal for the request.
public class JwtAuthentication : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
var authHeader=actionContext.Request.Headers.Authorization;
if (authHeader!=null&& !String.IsNullOrWhiteSpace(authHeader.Parameter))
System.Threading.Thread.CurrentPrincipal = JwtAuthenticationHandler.GetPrincipal(authHeader.Parameter);
return ClientAuthorize.Authorize(Roles);
}
}
Usage
[JwtAuthentication(Roles = "User")]
public class ChatBotController : ApiController
{}
Note i have been experiencing some issues with visual studio 2017 reading the Current Principal from the Thread.
You might have a look at if you still experience issues.
ClaimsPrincipal.Current Visual Studio 2017 different behavior
All I needed to do was to implement the unprotect function in the JWTFormat class
public AuthenticationTicket Unprotect(string protectedText)
{
try
{
var handler = new JwtSecurityTokenHandler();
AppContext = new AppContext(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString)
{
EventLogPriority = Properties.Settings.Default.EventLogPriority
};
SecurityToken validToken;
_validationParameters.IssuerSigningKey = new SymmetricSecurityKey(TextEncodings.Base64Url.Decode(Secret));
ClaimsPrincipal principal = handler.ValidateToken(protectedText, _validationParameters, out validToken);
var validJwt = validToken as JwtSecurityToken;
if (validJwt == null)
{
throw new ArgumentException("Invalid JWT");
}
ClaimsIdentity identity = principal.Identities.FirstOrDefault();
return new AuthenticationTicket(identity, new AuthenticationProperties());
}
catch (SecurityTokenException ex)
{
var msg = new HttpResponseMessage(HttpStatusCode.Unauthorized) { ReasonPhrase = "Access Token is manipulated" };
throw new HttpResponseException(msg);
}
}