AddJwtBearer validates token but kills request - asp.net-web-api

Following is my code to register authentication
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
ValidateIssuer = true,
ValidIssuer = issuer,
ValidateAudience = true,
ValidAudience = audience,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = tokenValidationParameters;
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
Console.WriteLine("OnAuthenticationFailed: " + context.Exception.Message);
Trace.WriteLine("OnAuthenticationFailed: " + context.Exception.Message);
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
Console.WriteLine("OnTokenValidated: " + context.SecurityToken);
Trace.WriteLine("OnTokenValidated: " + context.SecurityToken);
return Task.CompletedTask;
}
};
});
Following is my controller code
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
[Authorize]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
But my request to api/values never makes it to this controller.
Somehow the request completes in OnTokenValidated event.
I get 401 response.
What am I doing wrong here?

To my understanding, when using Identity the defaultAuthenticateScheme is set to cookie authentication.
I am unsure what options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; is supposed to do but it didn't change the DefaultAuthenticateScheme or DefaultChallengeScheme when I tested it in my program that is using JWT authentication with identity.
try adding in
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
and then to prevent getting a 404 instead of a 401 when not authorized add
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
if you want to use cookie authentication with JWT you can set the DefaultAuthenticatieScheme in the [Authorize] tag like so either:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

Related

Microsoft Teams App - pass team's culture to oauth backend provider in Tabs

I have build a Teams application on Asp.net core and connected the oauth backend provider. At startup.cs, I dont know the Team's culture yet. Then I ask user to login and get the culture in my cshtml page. But I am not able to find a way to pass Team's cultutre to my oauth backend.The settings goes like this in statup.cs:
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieScheme;
options.DefaultChallengeScheme = OAuthScheme;
options.DefaultAuthenticateScheme = CookieScheme;
options.DefaultSignInScheme = CookieScheme;
options.DefaultSignOutScheme = CookieScheme;
})
.AddCookie(CookieScheme, options =>
{
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.None;
options.ExpireTimeSpan = TimeSpan.FromMinutes(120);
options.SlidingExpiration = false;
options.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = RefreshTokenIfRequired
};
})
.AddOAuth(OAuthScheme, options =>
{
var settings = JsonConvert.DeserializeObject<OnlineApiSettings>(Configuration["settings"]);
options.ClientId = settings.ClientId;
options.ClientSecret = settings.ClientSecret;
options.AuthorizationEndpoint = settings.AuthEndpoint;
options.TokenEndpoint = settings.TokenEndpoint;
options.CallbackPath = new PathString("/oauth/callback");
options.SaveTokens = true;
});
This is how login page constructed:
<script type="text/javascript">
function loginV1() {
microsoftTeams.app.initialize().then(() => {
microsoftTeams.app.getContext().then((context) => {
login(window.location.origin + "/LoginPopup?culture=" + context.app.locale);
});
});
}
function login(url) {
const urlParams = new URLSearchParams(window.location.search);
const redirectUrl = urlParams.get('redirectPath');
microsoftTeams.authentication.authenticate({
url: url,
width: 600,
height: 535,
successCallback: function (result) {
console.log("Login succeeded: " + result);
window.location.href = "/" + redirectUrl;
},
failureCallback: function (reason) {
console.log("Login failed: " + reason);
window.location.href = "/" + redirectUrl;
}
});
}
</script>
and LoginPopup page is like below:
[Authorize]
public class LoginPopupModel : PageModel
{
private readonly IHttpContextAccessor _httpContext;
public LoginPopupModel(IHttpContextAccessor httpContextAccessor)
{
_httpContext = httpContextAccessor;
}
public async Task<IActionResult> OnGetAsync()
{
if (_httpContext.HttpContext.User.Identity.IsAuthenticated)
{
return RedirectToPage("LoginSuccess");
}
return null;
}
}

send custom object in response when jwt authentication fails in asp.net web api

I have created a ASP.NET WEB.API project with OWIN JWT Authentication. Is is working as expected, but what I want is to show custom validation message to use instead of default message if JWT authentication fails.
Right now it shows
{
"message": "Authorization has been denied for this request."
}
This is my Validation Code.
public class OwinStartUp
{
public void Configuration(IAppBuilder app)
{
HttpStatusCode statusCode;
HttpResponseMessage message = new HttpResponseMessage();
try
{
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "http://jwt.com",
ValidAudience = "http://jwt.com",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("jwt_key_asdf"))
}
});
}
catch (SecurityTokenValidationException e )
{
statusCode = HttpStatusCode.Unauthorized;
message = new HttpResponseMessage(HttpStatusCode.Unauthorized)
{
Content = new StringContent("Failed") /////// Even tried this but it does not work.
};
}
catch(Exception ex)
{
}
}
}

Jwt token always return 401 .net core 3.1 web api

I'm new at the Jwt. I create new web api running on 3.1 version and my configuration like that ;
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
var symmetricKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("mysupersecretkeytosharewithnooneandalwaysinsideapp"));
TokenValidationParameters tokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = "Management",
ValidateIssuer = true,
ValidAudience = "Management",
ValidateAudience = true,
ValidateLifetime = true,
IssuerSigningKey = symmetricKey,
ValidateIssuerSigningKey = true,
};
services.AddCors();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = tokenValidationParameters;
});
services.AddHttpContextAccessor();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors(q => q.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
And my token generator class like that ;
public static string GenerateToken(string jwtKey, string jwtExpireMinutes)
{
var claims = new List<Claim>()
{
new Claim(ClaimTypes.NameIdentifier,"C280298D-D896-49E2-96AD-5C82243F9048"),
new Claim(ClaimTypes.Email,"mfa#gmail.com"),
};
var keyByte = Encoding.UTF8.GetBytes(jwtKey);
var signInKey = new SymmetricSecurityKey(keyByte);
var expireMinutes = DateTime.Now.AddMinutes(Convert.ToDouble(jwtExpireMinutes));
var token = new JwtSecurityToken(claims: claims, expires: expireMinutes, signingCredentials: new SigningCredentials(signInKey, SecurityAlgorithms.HmacSha256));
return new JwtSecurityTokenHandler().WriteToken(token);
}
I tried on Postman and I'm getting token successfuly but with this token When I call my method it returns 401 unauthorized.
Controller has these attributes ;
[ApiController]
[Route("api/[controller]")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
These are the results ;
That is because you check the aud (audience) and iss (issuer) claim in your jwt token when validating token :
ValidIssuer = "Management",
ValidateIssuer = true,
ValidAudience = "Management",
ValidateAudience = true,
So that when creating token you should adding these correct claims ,otherwise token validation won't pass :
var token = new JwtSecurityToken(audience: "Management", issuer: "Management", claims: claims, expires: expireMinutes, signingCredentials: new SigningCredentials(signInKey, SecurityAlgorithms.HmacSha256));

IdentityServer4 Access token updating

Last week I am trying to configure the IdentityServer4 to get an access token automatically updating.
I had an API:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5100";
options.RequireHttpsMetadata = false;
options.ApiName = "api1";
});
My MVC client configuration:
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "http://localhost:5100";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.ClientSecret = "secret";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("api1");
options.Scope.Add("offline_access");
});
And the IdentityServer's clients configuration:
return new List<Client>
{
new Client
{
ClientId = "mvc",
ClientName = "My mvc",
AllowedGrantTypes = GrantTypes.Hybrid,
RequireConsent = false,
AccessTokenLifetime = 10,
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = { "http://localhost:5102/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:5102/signout-callback-oidc" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess,
"api1"
},
AllowOfflineAccess = true
}
};
On the client side I use AJAX queries to call the API to get/post/put/delete data. I add the access token to the request and get the result.
private async getAuthenticationHeader(): Promise<any> {
return axios.get('/token').then((response: any) => {
return { headers: { Authorization: `Bearer ${response.data}` } };
});
}
async getAsync<T>(url: string): Promise<T> {
return this.httpClient
.get(url, await this.getAuthenticationHeader())
.then((response: any) => response.data as T)
.catch((err: Error) => {
console.error(err);
throw err;
});
}
The access token is provided by the MVC client method:
[HttpGet("token")]
public async Task<string> GetAccessTokenAsync()
{
return await HttpContext.GetTokenAsync("access_token");
}
It works fine. After access token expired I get 401 on the client side, so it would be great to have an opportunity to update access token automatically when it was expired.
According to a documentation I supposed, that It can be reached by setting AllowOfflineAccess to true and adding suitable scope "offline_access".
Maybe I don't understand the right flow of the access and refresh tokens usages. Can I do it automatically or it is impossible? I suppose, that we can use refresh tokens in out queries, but I don't understand how.
I've read a lot of SO answers and github issues but I am still confused. Could you help me to figure out?
After investigation and communicating in comments I've found the answer. Before every API call I get the expite time and according to the result update access_token or return existing:
[HttpGet("config/accesstoken")]
public async Task<string> GetOrUpdateAccessTokenAsync()
{
var accessToken = await HttpContext.GetTokenAsync("access_token");
var expiredDate = DateTime.Parse(await HttpContext.GetTokenAsync("expires_at"), null, DateTimeStyles.RoundtripKind);
if (!((expiredDate - DateTime.Now).TotalMinutes < 1))
{
return accessToken;
}
lock (LockObject)
{
if (_expiredAt.HasValue && !((_expiredAt.Value - DateTime.Now).TotalMinutes < 1))
{
return accessToken;
}
var response = DiscoveryClient.GetAsync(_identitySettings.Authority).Result;
if (response.IsError)
{
throw new Exception(response.Error);
}
var tokenClient = new TokenClient(response.TokenEndpoint, _identitySettings.Id, _identitySettings.Secret);
var refreshToken = HttpContext.GetTokenAsync("refresh_token").Result;
var tokenResult = tokenClient.RequestRefreshTokenAsync(refreshToken).Result;
if (tokenResult.IsError)
{
throw new Exception();
}
accessToken = tokenResult.AccessToken;
var idToken = HttpContext.GetTokenAsync("id_token").Result;
var tokens = new List<AuthenticationToken>
{
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.IdToken,
Value = idToken
},
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.AccessToken,
Value = accessToken
},
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.RefreshToken,
Value = tokenResult.RefreshToken
}
};
var expiredAt = DateTime.UtcNow.AddSeconds(tokenResult.ExpiresIn);
tokens.Add(new AuthenticationToken
{
Name = "expires_at",
Value = expiredAt.ToString("o", CultureInfo.InvariantCulture)
});
var info = HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme).Result;
info.Properties.StoreTokens(tokens);
HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, info.Principal, info.Properties).Wait();
_expiredAt = expiredAt.ToLocalTime();
}
return accessToken;
}
}
I call this method to get the access_token and add int to the API call headers:
private async getAuthenticationHeader(): Promise<any> {
return axios.get('config/accesstoken').then((response: any) => {
return { headers: { Authorization: `Bearer ${response.data}` } };
});
}
async getAsync<T>(url: string): Promise<T> {
return this.axios
.get(url, await this.getAuthenticationHeader())
.then((response: any) => response.data as T)
.catch((err: Error) => {
console.error(err);
throw err;
});
}
Double check locking were implemented to prevent simultamious async API calls try to change access_token at the same time. Optionally you can cashe you access_token into static variable or cache, it is up to you.
If you have any advices or alternatives it would be insteresting to discuss. Hope it helps someone.
There's 2 ways of doing this:
Client side - Handle the authentication and obtaining of the token on the client side using a lib like oidc-client-js. This has a feature that allows automatic renewal of the token via a prompt=none call to the authorize endpoint behind the scenes.
Refresh token - store this in your existing cookie and then use it to request a new access token as needed. In this mode your client side code doing the AJAX calls would need to be aware of token errors and automatically request a new token from the server whereby GetAccessTokenAsync() could use the refresh token to get a new access token.

Simple Authentication using Jwt in dot net core MVC

I'm trying to add JWT validation in my dot net core application. I've followed this link to understand JWT and able to generate a token by givings some values like this.
var token = new JwtSecurityToken(
issuer: issuer,
audience: aud,
claims: claims,
expires: expTime,
signingCredentials: creds
);
Edit: and to follow this answer, I've also added JwtBearerAuthentication middleware in my app by adding app.UseJwtBearerAuthentication(new JwtBearerOptions { /* options */ }) to Startup.Configure() method.
Now I'm stuck how could I pass this token inside HTTP header? I'm generating this token on Login but whats next? How could I get to know that JWT is added and working fine??
Any kind of help will be appreciated.
This is a runnable sample for bearer token authentication in ASP.NET Core.
How to achieve a bearer token authentication and authorization in ASP.NET Core
At back end, you can generate the token following this code:
[Route("api/[controller]")]
public class TokenAuthController : Controller
{
[HttpPost]
public string GetAuthToken(User user)
{
var existUser = UserStorage.Users.FirstOrDefault(u => u.Username == user.Username && u.Password == user.Password);
if (existUser != null)
{
var requestAt = DateTime.Now;
var expiresIn = requestAt + TokenAuthOption.ExpiresSpan;
var token = GenerateToken(existUser, expiresIn);
return JsonConvert.SerializeObject(new {
stateCode = 1,
requertAt = requestAt,
expiresIn = TokenAuthOption.ExpiresSpan.TotalSeconds,
accessToken = token
});
}
else
{
return JsonConvert.SerializeObject(new { stateCode = -1, errors = "Username or password is invalid" });
}
}
private string GenerateToken(User user, DateTime expires)
{
var handler = new JwtSecurityTokenHandler();
ClaimsIdentity identity = new ClaimsIdentity(
new GenericIdentity(user.Username, "TokenAuth"),
new[] {
new Claim("ID", user.ID.ToString())
}
);
var securityToken = handler.CreateToken(new SecurityTokenDescriptor
{
Issuer = TokenAuthOption.Issuer,
Audience = TokenAuthOption.Audience,
SigningCredentials = TokenAuthOption.SigningCredentials,
Subject = identity,
Expires = expires
});
return handler.WriteToken(securityToken);
}
}
In Startup.cs/ConfigureServices method
services.AddAuthorization(auth =>
{
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme‌​)
.RequireAuthenticatedUser().Build());
});
And add this code in Configure method
app.UseJwtBearerAuthentication(new JwtBearerOptions {
TokenValidationParameters = new TokenValidationParameters {
IssuerSigningKey = TokenAuthOption.Key,
ValidAudience = TokenAuthOption.Audience,
ValidIssuer = TokenAuthOption.Issuer,
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(0)
}
});
At front end, you just add the token to header like this:
$.ajaxSetup({
headers: { "Authorization": "Bearer " + accessToken }
});
or
$.ajax("http://somedomain/somepath/somepage",{
headers:{ "Authorization": "Bearer " + accessToken },
/*some else parameter for ajax, see more you can review the Jquery API*/
});

Resources