how to add authorization to gRpc headers from Vue.js - vue.js

first of all . i created a gRpc service project from VS2019, and added Jwt Bearer Authentication and Authorization.
when client app (VS code + Vue.js) request login service method , a token will be sent.
string key = "AB6B4DC1-FABF-47AE-A585-4AD154758B05";
var securityKey = new SymmetricSecurityKey(Guid.Parse(key).ToByteArray());
var claims = new[] {
new Claim(ClaimTypes.Name, clientId),
new Claim(ClaimTypes.NameIdentifier,clientId)
};
var token = new JwtSecurityToken("client",
"client",
claims,
notBefore:DateTime.Now,
expires: DateTime.Now.AddSeconds(60),
signingCredentials: new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256)
);
return new JwtSecurityTokenHandler().WriteToken(token);
and then jwt configuration is
var key = "AB6B4DC1-FABF-47AE-A585-4AD154758B05";
options.TokenValidationParameters = new TokenValidationParameters
{
ClockSkew = TimeSpan.FromSeconds(30),
ValidateIssuer = true,
ValidateAudience = true,
AudienceValidator = (m, n, z) =>
{
return m != null && m.FirstOrDefault().Equals("");
},
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidAudience = "client",
ValidIssuer = "client",
IssuerSigningKey = new SymmetricSecurityKey(Guid.Parse(key).ToByteArray())
};
the problem is i can't pass authorization as the header item to gRpc Server
code bellow:
'
import { Metadata } from '#grpc/grpc-js/build/src/metadata'
import { CallCredentials } from '#grpc/grpc-js/build/src/call-credentials'
export default {
name: 'App',
created () {
this.client = new GreeterClient('https://localhost:5001', CallCredentials.createFromPlugin, null)
this.appClient = new AppClient('https://localhost:5001', null, null)
},
methods: {
loginSys () {
var lm = new LoginModel()
lm.setLoginname('admin')
lm.setLoginpwd('abc123!##')
lm.setLogincode('kkkkkrrr')
var appInfo = new AppInfo()
appInfo.setAppid('asdfasdfasdf')
appInfo.setApptoken('12345678901234567890')
appInfo.setModel(lm)
this.appClient.login(appInfo, {}, (err, response) => {
debugger
if (err) {
console.log(`Unexpected error for sayHello: code = ${err.code}, message = "${err.message}"`)
} else {
console.log(response.getMessage())
console.log(response.getIssucceed())
if (response.hasUserinfo()) {
var userInfo = response.getUserinfo()
console.log(userInfo.getAvatar())
console.log(userInfo.getUsername())
console.log(userInfo.getCellphone())
}
}
})
},
get1 () {
var token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiY2xpZW50SWQiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6ImNsaWVudElkIiwibmJmIjoxNjI5MDM0MzkxLCJleHAiOjE2MjkwMzQ0NTEsImlzcyI6IkFpMi5XZWIiLCJhdWQiOiJBaTIuV2ViIn0.1QpFsvzRvlBvTPNQ4hzETIDaLfmUsxmVvXaRyXVskjI'
var metadata = new Metadata()
metadata.add('authorization', 'Bearer ' + token)
var request = new HelloRequest()
request.setName('World')
this.client.sayHello(request, metadata, (err, response) => {
if (err) {
console.log(`Unexpected error for sayHello: code = ${err.code}, message = "${err.message}"`)
} else {
console.log(response.getMessage())
}
})
}
}
}
method : get1 is used to do the "authorization" test .
when i executed this method , i found authorization doesn't exist in http headers.
i don't know why , and what should i do ?

i got the answer.
const token = 'eyJhbGciOiJIUzyXVskjI'
const metadata = { 'authorization': 'Bearer ' + token }

Related

Multiple JWT Tokens and Multiple Auth Handlers

I currently have Auth in place for my API application using a JWT token with Identity User, Which works great, I am now trying to chain on another JWT Token which users keycloak and I want to setup A custom auth Handler just for that token type, is that possible? My Code looks as follows:
services.AddAuthentication()
.AddJwtBearer("KeyCloak", opt =>
{
opt.Authority = "site.co.za/auth/realms/Development";
opt.Audience = "dev";
opt.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidAudiences = new string[] { "dev" },
NameClaimType = "preferred_username",
RoleClaimType = "role"
};
opt.Events = new JwtBearerEvents()
{
OnAuthenticationFailed = c =>
{
c.NoResult();
c.Response.StatusCode = 500;
c.Response.ContentType = "text/plain";
return c.Response.WriteAsync(c.Exception.ToString());
}
};
opt.RequireHttpsMetadata = false;
opt.SaveToken = true;
opt.Validate();
})
.AddJwtBearer("Internal", opt =>
{
opt.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(Constants.AuthTokenKey))
};
});
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("KeyCloak", "Internal").Build();
});
I need to add a custom Auth HandleAuthenticateAsync just for keycloak and use the normal out of the box HandleAuthenticateAsync for internal. Is this possible to do?
You can do this:
this will select the jwt token validation based on API path.
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = "Custom";
})
.AddPolicyScheme("Custom", "Custom", options =>
{
options.ForwardDefaultSelector = context =>
{
bool isKeyCloakAuthRequired = context.Request.Path.StartsWithSegments("/apithatneedskeycloakauth");
if (isKeyCloakAuthRequired)
{
return "Keycloak";
}
else
{
return "Internal";
}
};
})
.AddJwtBearer("Keycloak", options =>
{
// your code for keycloak validation parameters.
})
.AddJwtBearer("Internal", options =>
{
// your code for token validation parameters.
})
Hope it helps.

Single sign out in identity server 4

###I am using identity server 4 for authentication for .net and angular apps.
if I log out from one client it does not log out from others.###
how can I delete the user session and implement single-signout for all clients
#the config class in identity server#
//MVC Client
new Client
{
ClientName = ".NET 4 MVC website",
ClientId = "net4mvcclient",
ClientSecrets =
{
new Secret("secret3".Sha256())
},
//Grant types are a way to specify how a client wants to interact with IdentityServer
AllowedGrantTypes = GrantTypes.Implicit,
RequireConsent = false,
AllowOfflineAccess = true,
AllowAccessTokensViaBrowser = true,
RedirectUris = { $"{_config.GetValue<string>("IdentityServerSettings:MVCBaseUri")}/signin-oidc" },
PostLogoutRedirectUris = { $"{_config.GetValue<string>("IdentityServerSettings:MVCBaseUri")}/" },
AllowedScopes = {"openid", "profile", "offline_access", "api1", "api2" } ,
AllowedCorsOrigins = {$"{_config.GetValue<string>("IdentityServerSettings:MVCBaseUri")}"},
AccessTokenLifetime = 50000
},
//angular_spa
new Client {
RequireConsent = false,
ClientId = "angular_spa",
ClientName = "Angular SPA",
AllowedGrantTypes = GrantTypes.Implicit,
AllowedScopes = { "openid", "profile", "offline_access", "api1", "api2" },
RedirectUris = { $"{_config.GetValue<string>("IdentityServerSettings:AngularBaseUri")}/#auth-callback/#" },
PostLogoutRedirectUris = { $"{_config.GetValue<string>("IdentityServerSettings:AngularBaseUri")}/" },
AllowedCorsOrigins = { $"{_config.GetValue<string>("IdentityServerSettings:AngularBaseUri")}" },
AllowAccessTokensViaBrowser = true,
AccessTokenLifetime = 3600
}
};
startup class in MVC client
public void ConfigureIdentityServer(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
Authority = ConfigurationManager.AppSettings["IdentityServerUrl"], //Identity server Url
ClientId = "net4mvcclient",
ClientSecret = "secret3",
RedirectUri = ConfigurationManager.AppSettings["HourlyMVCUrl"] +"/signin-oidc", //Net4MvcClient's URL
PostLogoutRedirectUri = ConfigurationManager.AppSettings["HourlyMVCUrl"]+"/", //MVC Client URL
ResponseType = "id_token token",
RequireHttpsMetadata = false,
Scope = "openid profile api1 api2 offline_access",
TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
NameClaimType = "name"
},
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = n =>
{
n.AuthenticationTicket.Identity.AddClaim(new Claim("access_token", n.ProtocolMessage.AccessToken));
n.AuthenticationTicket.Identity.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
return Task.FromResult(0);
},
RedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
{
var id_token_claim = n.OwinContext.Authentication.User.Claims.FirstOrDefault(x => x.Type == "id_token");
if (id_token_claim != null)
{
n.ProtocolMessage.IdTokenHint = id_token_claim.Value;
}
}
return Task.FromResult(0);
}
}
});
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
app.UseNLog((eventType) => LogLevel.Debug);
}
#the logout function in identity server#
public async Task<IActionResult> Logout(LogoutInputModel model)
{
// build a model so the logged out page knows what to display
var vm = await BuildLoggedOutViewModelAsync(model.LogoutId);
if (User?.Identity.IsAuthenticated == true)
{
// delete local authentication cookie
// Request.GetOwinContext().Authentication.SignOut();
await HttpContext.SignOutAsync();
await _signInManager.SignOutAsync();
// raise the logout event
await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
}
// check if we need to trigger sign-out at an upstream identity provider
if (vm.TriggerExternalSignout)
{
// build a return URL so the upstream provider will redirect back
// to us after the user has logged out. this allows us to then
// complete our single sign-out processing.
string url = Url.Action("Logout", new { logoutId = vm.LogoutId });
// this triggers a redirect to the external provider for sign-out
return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme);
}
if (vm.PostLogoutRedirectUri != null)
{
return SignOut(new AuthenticationProperties { RedirectUri = vm.PostLogoutRedirectUri }, vm.ExternalAuthenticationScheme);
}
return View("LoggedOut", vm);
}

404 error after Apple authentication using OpenId Connect

adopting from scottbrady91.com, I'm trying to have an Apple external authentication on our site. I've had Microsoft one working, but not the Apple one yet. The user is already directed to appleid.apple.com, but after authentication, it's returned to https://iluvrun.com/signin-apple (which is correct), but this isn't handled and so the user gets a 404 error.
To be honest I don't know how signin-facebook, signin-google or signin-oidc work, but they just do. So I have problems figuring out why signin-apple isn't being handled.
The site is built using ASP.NET Web Forms. Below is what I have at Startup.Auth.cs:
namespace ILR
{
public partial class Startup {
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions("Apple")
{
ClientId = "com.iluvrun.login",
Authority = "https://appleid.apple.com/auth/authorize",
SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
RedirectUri = "https://iluvrun.com/signin-apple",
PostLogoutRedirectUri = "https://iluvrun.com",
Scope = "name email",
ResponseType = OpenIdConnectResponseType.Code,
ResponseMode = OpenIdConnectResponseMode.FormPost,
CallbackPath = PathString.FromUriComponent("/signin-apple"),
Configuration = new OpenIdConnectConfiguration
{
AuthorizationEndpoint = "https://appleid.apple.com/auth/authorize",
TokenEndpoint = "https://appleid.apple.com/auth/token"
},
TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = "https://appleid.apple.com",
IssuerSigningKey = new JsonWebKeySet(GetKeysAsync().Result).Keys[0]
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = (context) =>
{
context.TokenEndpointRequest.ClientSecret = TokenGenerator.CreateNewToken();
return Task.CompletedTask;
},
AuthenticationFailed = (context) =>
{
context.HandleResponse();
context.Response.Redirect("/Account/Login?errormessage=" + context.Exception.Message);
return Task.FromResult(0);
}
},
ProtocolValidator = new OpenIdConnectProtocolValidator
{
RequireNonce = false,
RequireStateValidation = false
}
}
);
}
private static async Task<string> GetKeysAsync()
{
string jwks = await new HttpClient().GetStringAsync("https://appleid.apple.com/auth/keys");
return jwks;
}
}
public static class TokenGenerator
{
public static string CreateNewToken()
{
const string iss = "CHM57Z5A6";
const string aud = "https://appleid.apple.com";
const string sub = "com.iluvrun.login";
const string privateKey = "XXXX"; // contents of .p8 file
CngKey cngKey = CngKey.Import(Convert.FromBase64String(privateKey), CngKeyBlobFormat.Pkcs8PrivateBlob);
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
JwtSecurityToken token = handler.CreateJwtSecurityToken(
issuer: iss,
audience: aud,
subject: new ClaimsIdentity(new List<Claim> { new Claim("sub", sub) }),
expires: DateTime.UtcNow.AddMinutes(5),
issuedAt: DateTime.UtcNow,
notBefore: DateTime.UtcNow,
signingCredentials: new SigningCredentials(new ECDsaSecurityKey(new ECDsaCng(cngKey)), SecurityAlgorithms.EcdsaSha256));
return handler.WriteToken(token);
}
}
}
Does anyone have any clue what I miss to get this working?
You are on the right track and your question helped me to quick start my own solution for Apple ID OpenIdConnect OWIN integration in my project. After finding your post here it took me quite long to fix all issues.
After using your code I've got the same 404 error.
404 error
This was due to unhandled exception in CreateNewToken() method, which wasn't able to generate valid token. In my case it was missing Azure configuration for my AppService more described in CngKey.Import on azure:
WEBSITE_LOAD_USER_PROFILE = 1
After setting this configuration in Azure I moved to next issue:
Token endpoint wasn't called
This was due to missing configuration in OpenIdConnectAuthenticationOptions:
RedeemCode = true
This option trigger all the next processing of the authentication pipeline inside OpenIdConnect (TokenResponseReceived, SecurityTokenReceived, SecurityTokenValidated)
AccountController.ExternalLoginCallback token processing issue
This was tricky. Because all you get is after calling
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
is getting:
loginInfo == null
So after reading lot of issue articles on this topic like OWIN OpenID provider - GetExternalLoginInfo() returns null and tries, the only final workaround was to add https://github.com/Sustainsys/owin-cookie-saver to my Startup.cs, which fixed problem of missing token cookies. Its marked as legacy, but it was my only option to fix this.
So final OpenIdConnect options config for my working solution is:
var appleIdOptions = new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "https://appleid.apple.com",
ClientId = "[APPLE_CLIENT_ID_HERE]",
Authority = "https://appleid.apple.com/auth/authorize",
SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
RedirectUri = "https://www.europeanart.eu/signin-apple",
PostLogoutRedirectUri = "https://www.europeanart.eu",
Scope = OpenIdConnectScope.Email,
RedeemCode = true,
ResponseType = OpenIdConnectResponseType.CodeIdToken,
ResponseMode = OpenIdConnectResponseMode.FormPost,
CallbackPath = PathString.FromUriComponent("/signin-apple"),
Configuration = new OpenIdConnectConfiguration
{
AuthorizationEndpoint = "https://appleid.apple.com/auth/authorize",
TokenEndpoint = "https://appleid.apple.com/auth/token"
},
TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = "https://appleid.apple.com",
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
IssuerSigningKeys = new JsonWebKeySet(GetKeys()).Keys,
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = (context) =>
{
var clientToken = JwtTokenGenerator.CreateNewToken();
logger.LogInfo("Apple: clientToken generated");
context.TokenEndpointRequest.ClientSecret = clientToken;
logger.LogInfo("Apple: TokenEndpointRequest ready");
return Task.FromResult(0);
},
TokenResponseReceived = (context) =>
{
logger.LogInfo("Apple: TokenResponseReceived");
return Task.FromResult(0);
},
SecurityTokenReceived = (context) =>
{
logger.LogInfo("Apple: SecurityTokenReceived");
return Task.FromResult(0);
},
SecurityTokenValidated = (context) =>
{
string userID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
logger.LogInfo("Apple: SecurityTokenValidated with userID=" + userID);
return Task.FromResult(0);
},
RedirectToIdentityProvider = (context) =>
{
logger.LogInfo("Apple: RedirectToIdentityProvider");
if(context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
{
logger.LogInfo("Apple: RedirectToIdentityProvider -> Authenticate()");
}
else if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Token)
{
logger.LogInfo("Apple: RedirectToIdentityProvider -> Token()");
}
return Task.FromResult(0);
},
AuthenticationFailed = (context) =>
{
context.HandleResponse();
logger.LogError("Apple Authentication Failed.", context.Exception);
context.Response.Redirect("/Account/Login?errormessage=" + context.Exception.Message);
return Task.FromResult(0);
}
},
ProtocolValidator = new OpenIdConnectProtocolValidator
{
RequireNonce = false,
RequireStateValidation = false
}
};

Authentication & Authorization - Token in HTTP request body

I am trying to create a custom authentication handler that will require the Bearer JWT in the body of an HTTP request, but I'd prefer not to create a whole new custom authorization. Unfortunately, the only thing I can do is read the HTTP request body, get the token from there and put it in the Authorization header of the request.
Is there a different, more efficient way to do it? All I managed is to find the default JwtBearerHandler implementation on GitHub but when I make some modifications, it can't read the principal properly.
Startup.cs:
services.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = true;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
RequireExpirationTime = true,
ClockSkew = TimeSpan.FromSeconds(30)
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = ctx =>
{
if (ctx.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
ctx.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
});
public class AuthHandler : JwtBearerHandler
{
private readonly IRepositoryEvonaUser _repositoryUser;
private OpenIdConnectConfiguration _configuration;
public AuthHandler(IOptionsMonitor<JwtBearerOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
IDataProtectionProvider dataProtection,
ISystemClock clock,
IRepositoryUser repositoryUser,
OpenIdConnectConfiguration configuration
)
: base(options, logger, encoder, dataProtection, clock)
{
_repositoryUser = repositoryUser;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
string token = null;
try
{
var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options);
await Events.MessageReceived(messageReceivedContext);
if (messageReceivedContext.Result != null)
{
return messageReceivedContext.Result;
}
token = messageReceivedContext.Token;
if (string.IsNullOrEmpty(token))
{
Request.EnableBuffering();
using (var reader = new StreamReader(Request.Body, Encoding.UTF8, true, 10, true))
{
var jsonBody = reader.ReadToEnd();
var body = JsonConvert.DeserializeObject<BaseRequest>(jsonBody);
if (body != null)
{
token = body.Token;
}
Request.Body.Position = 0;
}
if (string.IsNullOrEmpty(token))
{
return AuthenticateResult.NoResult();
}
}
if (_configuration == null && Options.ConfigurationManager != null)
{
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
}
var validationParameters = Options.TokenValidationParameters.Clone();
if (_configuration != null)
{
var issuers = new[] { _configuration.Issuer };
validationParameters.ValidIssuers = validationParameters.ValidIssuers?.Concat(issuers) ?? issuers;
}
List<Exception> validationFailures = null;
SecurityToken validatedToken;
foreach (var validator in Options.SecurityTokenValidators)
{
if (validator.CanReadToken(token))
{
ClaimsPrincipal principal; // it can't find this
try
{
principal = validator.ValidateToken(token, validationParameters, out validatedToken);
}
catch (Exception ex)
{
if (Options.RefreshOnIssuerKeyNotFound && Options.ConfigurationManager != null
&& ex is SecurityTokenSignatureKeyNotFoundException)
{
Options.ConfigurationManager.RequestRefresh();
}
if (validationFailures == null)
{
validationFailures = new List<Exception>(1);
}
validationFailures.Add(ex);
continue;
}
var tokenValidatedContext = new TokenValidatedContext(Context, Scheme, Options)
{
Principal = principal,
SecurityToken = validatedToken
};
await Events.TokenValidated(tokenValidatedContext);
if (tokenValidatedContext.Result != null)
{
return tokenValidatedContext.Result;
}
if (Options.SaveToken)
{
tokenValidatedContext.Properties.StoreTokens(new[]
{
new AuthenticationToken { Name = "access_token", Value = token }
});
}
tokenValidatedContext.Success();
return tokenValidatedContext.Result;
}
}
if (validationFailures != null)
{
var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
{
Exception = (validationFailures.Count == 1) ? validationFailures[0] : new AggregateException(validationFailures)
};
await Events.AuthenticationFailed(authenticationFailedContext);
if (authenticationFailedContext.Result != null)
{
return authenticationFailedContext.Result;
}
return AuthenticateResult.Fail(authenticationFailedContext.Exception);
}
return AuthenticateResult.Fail("No SecurityTokenValidator available for token: " + token ?? "[null]");
}
catch (Exception ex)
{
var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
{
Exception = ex
};
await Events.AuthenticationFailed(authenticationFailedContext);
if (authenticationFailedContext.Result != null)
{
return authenticationFailedContext.Result;
}
throw;
}
}
}
Or, is there a way to just tell the application to expect a JWT in the HTTP request body? I am well aware that the token should be sent in the request header instead of body, but I am interested into seeing if (and if so, how) this can be implemented.
I also tried this:
OnMessageReceived = ctx =>
{
ctx.Request.EnableBuffering();
using (var reader = new StreamReader(ctx.Request.Body, Encoding.UTF8, true, 10, true))
{
var jsonBody = reader.ReadToEnd();
var body = JsonConvert.DeserializeObject<BaseRequest>(jsonBody);
if (body != null)
{
ctx.Token = body.Token;
ctx.Request.Body.Position = 0;
}
}
return Task.CompletedTask;
}
By default , AddJwtBearer will get token from request header , you should write your logic to read token from request body and validate the token . That means no such configuration to "tell" middleware to read token form request body .
If token is sent in request body , you need to read the request body in middleware and put token in header before the jwt middleware reaches. Or read the request body in one of the jwt bearer middleware's event , for example , OnMessageReceived event , read token in request body and at last set token like : context.Token = token; . Here is code sample for reading request body in middleware .
I'll mark #Nan Yu's answer as the correct one, but I'll post my final code nonetheless. What I essentially did was revert back to the default JwtBearerHandler and use JwtBearerOptions and JwtBearerEvents's OnMessageReceived event to get the token value from HTTP request's body.
They all reside in the Microsoft.AspNetCore.Authentication.JwtBearer namespace.
services
.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = true;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
RequireExpirationTime = true,
ClockSkew = TimeSpan.Zero
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = ctx =>
{
if (ctx.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
ctx.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
},
OnMessageReceived = ctx =>
{
ctx.Request.EnableBuffering();
using (var reader = new StreamReader(ctx.Request.Body, Encoding.UTF8, true, 1024, true))
{
var jsonBody = reader.ReadToEnd();
var body = JsonConvert.DeserializeObject<BaseRequest>(jsonBody);
ctx.Request.Body.Position = 0;
if (body != null)
{
ctx.Token = body.Token;
}
}
return Task.CompletedTask;
}
};
});

Asp.Net Core & JWT authentication: How to know authentication failed because token expired?

Below is the JWT authentication I am using:
.AddJwtBearer(options =>
{
// options.SaveToken = false;
// options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(AuthConfig.GetSecretKey(Configuration)),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
};
options.Events = new JwtBearerEvents()
{
OnChallenge = c =>
{
c.HandleResponse();
// TODO: How to know if the token was expired?
return AspNetUtils.WriteJsonAsync(c.Response, new Result<string>
{
Message = "Unauthenticated.",
IsError = true
}, 401);
},
};
});
The authentication is working fine. For new requirements, I need to know if authentication failed because the JWT token was expired or not.
Note that authentication failed because of multi reasons. The token can be missing, tampered, or expired.
Any ideas? Thanks!
.AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents()
{
OnAuthenticationFailed = context =>
{
if(context.Exception is SecurityTokenExpiredException)
{
// if you end up here, you know that the token is expired
}
}
};
})
Using OnChallenge property:
.AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnChallenge = context =>
{
if (context?.AuthenticateFailure is SecurityTokenExpiredException)
{
var error = context.Error; // "invalid_token"
var errorDescription = context.ErrorDescription; // "The token is expired"
}
return Task.CompletedTask;
}
};
});