Resolve Invalid Scope Error Identity Server 6 - razor-pages

I am new to Identity server, recently set it up for a project, but I keep getting the following error
Sorry, there was an error : invalid_scope Invalid scope
These are the components that comprise the application.
Web Client -> ASPNETCORE Razor Pages application (Port: 7091)
Ocelot -> API Gateway
Identity Server 6 (Port: 5001)
StripeDotNet -> API
Basket -> API
My configurations/code are as follows:
Identity Server
public static class Config
{
public static IEnumerable<IdentityResource> IdentityResources =>
new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
// new IdentityResources.Email(),
};
public static IEnumerable<ApiScope> ApiScopes =>
new List<ApiScope>
{
new ApiScope("stripedotnetapi", "StripeDotNet API")
};
public static IEnumerable<Client> Clients =>
new List<Client>
{
// interactive ASP.NET Core MVC client
new Client
{
ClientId = "razorweb",
ClientName = "Razor Web",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.Code,
// where to redirect to after login
RedirectUris = { "https://localhost:7091/signin-oidc" },
//FrontChannelLogoutUri = "https://localhost:7091/signout-callback-oidc",
// where to redirect to after logout
PostLogoutRedirectUris = { "https://localhost:7091/signout-callback-oidc" },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
// IdentityServerConstants.StandardScopes.Email,
"stripedotnetapi"
}
}
};
}
Identity Server: Hosting Extensions
builder.Services
.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
// see https://docs.duendesoftware.com/identityserver/v6/fundamentals/resources/
options.EmitStaticAudienceClaim = true;
})
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients)
.AddAspNetIdentity<ApplicationUser>();
StripeDotNet API
public static IServiceCollection AddSecurityServices(this IServiceCollection services)
{
services.AddAuthentication("Bearer")
.AddJwtBearer(options =>
{
options.Authority = "https://localhost:5001";
options.TokenValidationParameters.ValidateAudience = false;
});
services.AddAuthorization(options =>
{
options.AddPolicy("ApiScope", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("scope", "stripedotnetapi");
});
});
return services;
}
StripeDotNet API: Controller Code
[Route("api/[controller]")]
[Authorize("ApiScope")]
public class CheckoutController : BaseController
{
private readonly ICheckoutService _checkoutService;
public CheckoutController(ICheckoutService checkoutService)
{
_checkoutService = Guard.Against.Null(checkoutService, nameof(checkoutService));
}
[HttpGet]
public async Task<IActionResult> CreateCheckoutSession([FromBody] CreateCheckoutSessionRequest req)
{
var response = await _checkoutService.CreateCheckoutSessionAsync(req.TenantId, req.PriceId,
req.SuccessUrl, req.CancelUrl);
return Ok(response);
}
[HttpGet("{sessionId}")]
public async Task<IActionResult> GetCheckoutSession(string sessionId)
{
var response = await _checkoutService.GetCheckoutSessionAsync(sessionId);
return Ok(response);
}
}
Ocelot API Gateway
var authenticationProviderKey = "IdentityApiKey";
builder.Services.AddAuthentication()
.AddJwtBearer(authenticationProviderKey, x =>
{
x.Authority = "https://localhost:5001"; // IDENTITY SERVER URL
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
});
Ocelot API Gateway: Configuration file
{
"UpStreamPathTemplate": "/api/Checkout",
"UpstreamHttpMethod": [ "Get" ],
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 7056
}
],
"DownstreamPathTemplate": "/api/Checkout",
"AuthenticationOptions": {
"AuthenticationProviderKey": "IdentityApiKey",
"AllowedScopes": []
}
},
{
"UpStreamPathTemplate": "/api/Checkout/{sessionId}",
"UpstreamHttpMethod": [ "Get" ],
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 7056
}
],
"DownstreamPathTemplate": "/api/Checkout/{sessionId}",
"AuthenticationOptions": {
"AuthenticationProviderKey": "IdentityApiKey",
"AllowedScopes": []
}
},
Web Client
public static IServiceCollection AddSecurityServices(this IServiceCollection services)
{
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:5001";
options.ClientId = "razorweb";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
//options.Scope.Add("email");
options.Scope.Add("stripedotnetapi");
options.Scope.Add("offline_access");
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
});
return services;
}
My discovery endpoint shows these items as valid scopes
"scopes_supported": [
"openid",
"profile",
"stripedotnetapi",
"offline_access"
],
The supported scopes appear to be setup correctly for the web client, but I keep getting an invalid scope error. Any guidance would be greatly appreciated.

Solved. I didnt pay close enough attention to the docs. Offline Access was not granted to the client.
AllowOfflineAccess = true,

Related

How to call downstream API in .Net Core using on behalf of flow?

Net core application and react. I have react app which calls my middle tier api and middle tier api calls down stream api. I have registered three apps in azure ad. I have made all the configurations. In middle tier API I am validating the token recieved from react app and trying to get token for downstream api as below.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddTransient<DownStreamAPIService>();
services.AddHttpClient();
services.AddOptions();
var azureAd = Configuration.GetSection("AzureAd").Get<AzureAd>();
IdentityModelEventSource.ShowPII = true;
services.AddMicrosoftIdentityWebApiAuthentication(Configuration)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = true;
options.Authority = $"{azureAd.Instance}/{azureAd.TenantId}/v2.0";
options.Audience = $"{azureAd.ClientId}";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = false,
ValidateActor = false
};
});
services.AddCors(options =>
{
options.AddPolicy(
"CorsPolicy",
builder =>
{
builder
.WithOrigins("https://localhost:3000")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
}
Below is DownStreamAPIService class
public async Task<JArray> GetApiDataAsync()
{
var client = _clientFactory.CreateClient();
// user_impersonation access_as_user access_as_application .default
var scope = _configuration["DownStreamAPI:ScopeForAccessToken"];
var accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(new[] { scope }).ConfigureAwait(false);
client.BaseAddress = new Uri(_configuration["DownStreamAPI:ApiBaseAddress"]);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await client.GetAsync("weatherforecast").ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var data = JArray.Parse(responseContent);
return data;
}
throw new ApplicationException($"Status code: {response.StatusCode}, Error: {response.ReasonPhrase}");
}
Below is appsettings.json file
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "mydomain.onmicrosoft.com",
"TenantId": "c5fff990-0e0a-4bf6-9c04-79ab98e05931",
"ClientId": "43dg8b3c-8743-4d6e-84b4-00fe04d93222"
},
"WebOriginUrl": "https://localhost:3000",
"DownStreamAPI": {
"ScopeForAccessToken": "api://723fac69-4038-4e16-92cc-3f57b7cc2381/access_downstream_api_as_user",
"ApiBaseAddress": "https://localhost:44316"
}
When I run the app I get below error
Scheme already exists: Bearer
In my middle tier app I am validating the token and trying to obtain new token for downstream API. i am just confused weather validating token required in middle tier api or not. I am using on behalf of flow. Can soneone help if there is anything wrong in this design. Any help would be appreciated. Thanks
When we use the AddMicrosoftIdentityWebApiAuthentication to configure Azure AD, the project will use Bearer scheme to do auth. We do not need to configure it again. For more details, please refer to here and here. So please remove the code
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = true;
options.Authority = $"{azureAd.Instance}/{azureAd.TenantId}/v2.0";
options.Audience = $"{azureAd.ClientId}";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = false,
ValidateActor = false
};
});
if you want to validate the token, you can use the following code
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = true,
ValidAudiences = new string[] { "872ebcec-c24a-4399-835a-201cdaf7d68b", "api://872ebcec-c24a-4399-835a-201cdaf7d68b" },
ValidateLifetime = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = false,
ValidateActor = false,
ValidIssuers = new string[] { "https://sts.windows.net/{tenantId}", "https://login.microsoftonline.com/{tenantId}/v2.0" }
};
});

Is there a way to set dynamically set the AddOpenIdConnect options in ConfigureServices in.net Core for IdentityServer4

I'm new to .NETCore and am using IdentityServer4 for authentication in a .NETCore web app and I need to be able to either dynamically set the ClientId or redirectUrls (from the login/logout page) based on the url of the web app. But there is no way to access HttpContext within ConfigureServices method or access the AddAuthentication options outside of ConfigureServices - I'm really stuck!
public void ConfigureServices(IServiceCollection services)
{
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<ISessionHelper, SessionHelper.SessionHelper>();
services.AddSingleton<PortalSetup>();
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie("Cookies", options =>
{
options.LoginPath = "/account/login";
options.LogoutPath = "/account/logoff";
})
.AddOpenIdConnect(options =>
{
options.SignInScheme = Configuration["Oidc:SignInScheme"];
options.Authority = Configuration["Oidc:Authority"];
options.MetadataAddress = $"{Configuration["Oidc:Authority"]}/.well-known/openid-configuration";
options.RequireHttpsMetadata = Convert.ToBoolean(Configuration["Oidc:RequireHttpsMetadata"]);
options.ClientId = Configuration["Oidc:ClientId"];
options.ResponseType = Configuration["Oidc:ResponseType"];
options.SaveTokens = Convert.ToBoolean(Configuration["Oidc:SaveTokens"]);
options.GetClaimsFromUserInfoEndpoint = Convert.ToBoolean(Configuration["Oidc:GetClaimsFromUserEndpoint"]);
options.ClientSecret = Configuration["Oidc:ClientSecret"];
foreach (var s in Configuration["Oidc:Scopes"].Split(','))
{
options.Scope.Add(s);
}
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.Configure<IISOptions>(iis =>
{
iis.AuthenticationDisplayName = "Windows";
iis.AutomaticAuthentication = false;
iis.ForwardClientCertificate = false;
});
services.AddScoped<ActionExceptionFilter>();
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(5); // set the time for session timeout here
});
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(Configuration["keysDirectory"]));
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ISessionHelper session)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
app.UseAuthentication();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseFileServer(new FileServerOptions
{
FileProvider = new PhysicalFileProvider(Configuration["ImageDirectory"]),
EnableDirectoryBrowsing = false,
RequestPath = new PathString("/desimages")
});
//enable session before mvc
app.UseSession();
app.UseMiddleware<PortalSetup>();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Store}/{id?}");
});
}
I ended up customising the standard OIDC middleware to take those parameters at runtime via the ChallengeAsync call. It’s actually pretty straight forward.

ASP NET CORE 2.1 IIS LOCALHOST CONNECTION REFUSED

I can run my application on Visual Studio 2017 but when I load it on to IIS I get localhost connection refused error. It is an app with login page and I created this with identity scaffold.Could you please help!!!
IdentityHostingStartup.cs
public class IdentityHostingStartup : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
builder.ConfigureServices((context, services) => {
services.AddDbContext<StockControlContext>(options =>
options.UseSqlServer(
context.Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<StockControlUser>()
.AddEntityFrameworkStores<StockControlContext>().AddDefaultTokenProviders().AddDefaultUI();
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = false;
options.Password.RequiredLength = 0;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
options.Password.RequiredUniqueChars = 0;
});
services.Configure<IISOptions>(options =>
{
options.ForwardClientCertificate= false;
});
});
}
}
}
startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var environment = services.BuildServiceProvider().GetRequiredService<IHostingEnvironment>();
services.ConfigureApplicationCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
});
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
/* services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();*/
services.AddDbContext<BAR_ERPContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddMvc().AddSessionStateTempDataProvider();
services.AddSession(options => {
options.Cookie.HttpOnly = true;
});
services.AddBootstrapPagerGenerator(options =>
{
// Use default pager options.
options.ConfigureDefault();
});
services.Configure<PagerOptions>(Configuration.GetSection("Pager"));
services.Configure<IISOptions>(options =>
{
options.ForwardClientCertificate = false;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseSession();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
program.cs
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
I added below code for both startup.cs and IdentityHostingStartup.cs
services.Configure<IISOptions>(options =>
{
options.ForwardClientCertificate = false;
});
I also tried creating new project without login page in asp.net core. It worked fine on IIS. But asp.net core project with scafolded identity anonymous login is not working on IIS, however it works fine on VS 2017.
Could you please help thx.

Asp.Net Core 1.0 authentication migrating to 2.0

I have a web API that I want to migrate to Asp.Net Core 2.0. The API is secured and I want to migrate it to 2.0, because we finished first circle. I tried something, but when I protect my controller with the [Authenticate] attribute, the controller at the given endpoint never get called, because the user is not authenticated.
public partial class Startup
{
public IConfigurationRoot Configuration { get; set; }
private static JwtOptions _jwtOptions;
private readonly IUserService _userService = new UserService();
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//Add DI and other services
SetServices(services);
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedProto;
});
services.AddAuthentication(scheme =>
{
scheme.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "aaa",
ValidAudience = "bbb",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("secret key"))
};
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(365);
options.Events = new CustomCookieAuthenticationEvents();
options.Cookie.Name = "access_token";
});
services.Configure<CookieAuthenticationOptions>(options =>
{
options.Events = new Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationEvents()
{
OnRedirectToLogin = ctx =>
{
if (ctx.Request.Path.StartsWithSegments("/api") && ctx.Response.StatusCode == 200)
{
ctx.Response.StatusCode = (int)System.Net.HttpStatusCode.Unauthorized;
return Task.FromResult<object>(null);
}
else
{
ctx.Response.Redirect(ctx.RedirectUri);
return Task.FromResult<object>(null);
}
}
};
});
//Logger
services.AddMvc(options =>
{
options.Filters.Add(new Loging.ApiExceptionFilter());
});
services.AddAuthentication(options =>
{
options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
});
// Add framework services.
MvcOptions mvcOptions = new MvcOptions();
mvcOptions.Filters.Add(new RequireHttpsAttribute());
MvcJsonOptions jsonOptions = new MvcJsonOptions();
jsonOptions.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
jsonOptions.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
services.AddMvc(options => options = mvcOptions).AddJsonOptions(options => options = jsonOptions);
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromDays(365);
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IOptions<JwtOptions> jwtOptions)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseDeveloperExceptionPage();
_jwtOptions = jwtOptions.Value;
app.UseAuthentication();
ConfigureAuth(app);
app.Map(new PathString("/api/images"), x => x.UseBlobFileViewHandler());
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseSession();
app.UseMvc(routes =>
{
routes.MapRoute
(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}"
);
routes.MapSpaFallbackRoute("spa-fallback", new { controller = "Home", action = "Index" });
});
}
As you can see I tried to do some modification on the startup.cs class, but still can't figure out how it works. In the documentation everywhere is EF. What about us, who don't want to use the EF implementation.
public partial class Startup
{
private void ConfigureAuth(IApplicationBuilder app)
{
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_jwtOptions.SecretKey));
TokenProviderOptions tokenProviderOptions = new TokenProviderOptions
{
Path = "/api/token",
Audience = _jwtOptions.Audience,
Issuer = _jwtOptions.Issuer,
SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256),
IdentityResolver = GetIdentity
};
TokenValidationParameters tokenValidationParameters = new TokenValidationParameters
{
// The signing key must match!
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
// Validate the JWT Issuer (iss) claim
ValidateIssuer = true,
ValidIssuer = _jwtOptions.Issuer,
// Validate the JWT Audience (aud) claim
ValidateAudience = true,
ValidAudience = _jwtOptions.Audience,
// Validate the token expiry
ValidateLifetime = true,
LifetimeValidator = LifetimeValidator,
// If you want to allow a certain amount of clock drift, set that here:
ClockSkew = TimeSpan.Zero
};
app.UseSimpleTokenProvider(tokenProviderOptions, tokenValidationParameters);
}
private Task<ClaimsIdentity> GetIdentity(string email)
{
ServiceMessage<UserEntity> request = _userService.FindByEmailAsync(email).Result;
if (request != null && request.Success && request.ResultObject != null)
{
return Task.FromResult(CreateClaimsIdentity(request.ResultObject, "Token"));
}
// Credentials are invalid, or account doesn't exist
return Task.FromResult<ClaimsIdentity>(null);
}
private ClaimsIdentity CreateClaimsIdentity(UserEntity user, string authenticationType)
{
List<Claim> claimCollection = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Email, ClaimValueTypes.String),
new Claim(ClaimTypes.Role, user.Role, ClaimValueTypes.String),
new Claim(ClaimTypes.Name, user.Email.Split('#')[0], ClaimValueTypes.String),
new Claim(ClaimTypes.Expiration, DateTime.UtcNow.AddDays(365).Second.ToString(), ClaimValueTypes.DaytimeDuration)
};
ClaimsIdentity claimsIdentity = new ClaimsIdentity(claimCollection, authenticationType);
return claimsIdentity;
}
private bool LifetimeValidator(DateTime? notBefore, DateTime? expires, SecurityToken token, TokenValidationParameters #params)
{
if (expires != null)
{
return expires > DateTime.UtcNow;
}
return false;
}
public static string FromHex()
{
string hex = Guid.NewGuid().ToString();
hex = hex.Replace("-", "");
byte[] raw = new byte[hex.Length / 2];
for (int i = 0; i < raw.Length; i++)
{
raw[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
}
return Encoding.ASCII.GetString(raw);
}
}
public class CustomCookieAuthenticationEvents : CookieAuthenticationEvents
{
public override Task RedirectToLogin(RedirectContext<CookieAuthenticationOptions> context)
{
if (context.HttpContext.Request.Path.StartsWithSegments("/api") && context.HttpContext.Response.StatusCode == 200)
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
//return base.RedirectToLogin(context);
return Task.FromResult((int)HttpStatusCode.Unauthorized);
}
}
public class CustomJwtDataFormat : ISecureDataFormat<AuthenticationTicket>
{
private readonly string algorithm;
private readonly TokenValidationParameters validationParameters;
public CustomJwtDataFormat(string algorithm, TokenValidationParameters validationParameters)
{
this.algorithm = algorithm;
this.validationParameters = validationParameters;
}
public AuthenticationTicket Unprotect(string protectedText) => Unprotect(protectedText, null);
public AuthenticationTicket Unprotect(string protectedText, string purpose)
{
var handler = new JwtSecurityTokenHandler();
ClaimsPrincipal principal = null;
SecurityToken validToken = null;
try
{
principal = handler.ValidateToken(protectedText, this.validationParameters, out validToken);
var validJwt = validToken as JwtSecurityToken;
if (validJwt == null)
{
throw new ArgumentException("Invalid JWT");
}
if (!validJwt.Header.Alg.Equals(algorithm, StringComparison.Ordinal))
{
throw new ArgumentException($"Algorithm must be '{algorithm}'");
}
// Additional custom validation of JWT claims here (if any)
}
catch (SecurityTokenValidationException)
{
return null;
}
catch (ArgumentException)
{
return null;
}
// Validation passed. Return a valid AuthenticationTicket:
return new AuthenticationTicket(principal, new Microsoft.AspNetCore.Authentication.AuthenticationProperties(), "Cookie");
}
// This ISecureDataFormat implementation is decode-only
public string Protect(AuthenticationTicket data)
{
throw new NotImplementedException();
}
public string Protect(AuthenticationTicket data, string purpose)
{
throw new NotImplementedException();
}
}
}
public class TokenProviderMiddleware
{
private readonly RequestDelegate _next;
private readonly TokenProviderOptions _options;
private readonly ILogger _logger;
private readonly JsonSerializerSettings _serializerSettings;
private readonly TokenValidationParameters _tokenValidationParameters;
private readonly ISocialAuthentificationServices _socialAuthentificationServices;
public TokenProviderMiddleware(RequestDelegate next, IOptions<TokenProviderOptions> options, ILoggerFactory loggerFactory, ISocialAuthentificationServices socialAuthentificationServices, IOptions<TokenValidationParameters> tokenValidationParameters)
{
_socialAuthentificationServices = socialAuthentificationServices;
_next = next;
_logger = loggerFactory.CreateLogger<TokenProviderMiddleware>();
_options = options.Value;
_tokenValidationParameters = tokenValidationParameters.Value;
ThrowIfInvalidOptions(_options);
_serializerSettings = new JsonSerializerSettings
{
Formatting = Formatting.Indented
};
}
public Task Invoke(HttpContext context)
{
//Add CORS to every response
context.Response.Headers.Add("Access-Control-Allow-Headers", new string[] { "Authorization", "Content-Type" });
context.Response.Headers.Add("Access-Control-Allow-Methods", new string[] { "OPTIONS", "POST", "GET", "DELETE", "PUT" });
context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
if (context.Request.Method.Equals("OPTIONS", StringComparison.Ordinal))
{
context.Response.StatusCode = 204;
return _next(context);
}
// If the request path doesn't match, skip
if (!context.Request.Path.Equals(_options.Path, StringComparison.Ordinal))
{
return _next(context);
}
// Request must be POST with Content-Type: application/x-www-form-urlencoded
if (!context.Request.Method.Equals("POST") || !context.Request.HasFormContentType)
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
return context.Response.WriteAsync("Bad request.");
}
_logger.LogInformation("Handling request: " + context.Request.Path);
return GetToken(context);
}
private async Task GetToken(HttpContext context)
{
TokenData headers = GetHeaderContext(context);
if (headers == null)
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync("Invalid encrypted token.");
return;
}
if (string.IsNullOrEmpty(headers.Provider))
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync("Provider not definied.");
return;
}
else if (string.IsNullOrEmpty(headers.Token))
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync("Invalid token.");
return;
}
else
{
var providers = (Mapurija.Models.Enum.Providers[])Enum.GetValues(typeof(Mapurija.Models.Enum.Providers));
if (!headers.Provider.Contains(headers.Provider))
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync("Invalid token.");
return;
}
}
ServiceMessage<UserEntity> validation = null;
int enumProvider = 0;
int.TryParse(headers.Provider, out enumProvider);
try
{
switch (enumProvider)
{
case (int)Mapurija.Models.Enum.Providers.Mapporia:
validation = await _socialAuthentificationServices.VerifyMapurijaTokenAsync(headers.Token);
break;
case (int)Mapurija.Models.Enum.Providers.Facebook:
validation = await _socialAuthentificationServices.VerifyFacebookTokenAsync(headers.Token);
break;
case (int)Mapurija.Models.Enum.Providers.Google:
validation = await _socialAuthentificationServices.VerifyFacebookTokenAsync(headers.Token);
break;
default:
validation = null;
break;
}
}
catch
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync("Invalid request token!");
return;
}
if (validation == null || !validation.Success || validation.ResultObject == null)
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync(validation.ErrorMessage);
return;
}
ClaimsIdentity identity = await _options.IdentityResolver(validation.ResultObject.Email);
if (identity == null)
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync("Invalid token.");
return;
}
DateTime now = DateTime.UtcNow;
// Specifically add the jti (nonce), iat (issued timestamp), and sub (subject/user) claims.
// You can add other claims here, if you want:
Claim[] claims = new Claim[]
{
new Claim(ClaimTypes.Name,validation.ResultObject.Email,ClaimValueTypes.String),
new Claim(JwtRegisteredClaimNames.Sub,validation.ResultObject.Email,ClaimValueTypes.String),
new Claim(JwtRegisteredClaimNames.Typ, validation.ResultObject.Role),
new Claim(JwtRegisteredClaimNames.Jti, await _options.NonceGenerator()),
new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(now).ToString(), ClaimValueTypes.Integer64),
new Claim(JwtRegisteredClaimNames.Iss, _options.Issuer),
new Claim(JwtRegisteredClaimNames.Aud, _options.Audience)
};
// Create the JWT and write it to a string
JwtSecurityToken jwt = new JwtSecurityToken
(
issuer: _options.Issuer,
audience: _options.Audience,
claims: claims,
notBefore: now,
expires: now.Add(_options.Expiration),
signingCredentials: _options.SigningCredentials
);
SecurityToken token;
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
string encodedJwt = handler.WriteToken(jwt);
ClaimsPrincipal principal = handler.ValidateToken(encodedJwt, _tokenValidationParameters, out token);
if (token == null)
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync("Invalid token generated!");
return;
}
var response = new
{
access_token = encodedJwt,
expires_in = (int)_options.Expiration.TotalSeconds
};
// Serialize and return the response
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(response, _serializerSettings));
}
private static void ThrowIfInvalidOptions(TokenProviderOptions options)
{
if (string.IsNullOrEmpty(options.Path))
{
throw new ArgumentNullException(nameof(TokenProviderOptions.Path));
}
if (string.IsNullOrEmpty(options.Issuer))
{
throw new ArgumentNullException(nameof(TokenProviderOptions.Issuer));
}
if (string.IsNullOrEmpty(options.Audience))
{
throw new ArgumentNullException(nameof(TokenProviderOptions.Audience));
}
if (options.Expiration == TimeSpan.Zero)
{
throw new ArgumentException("Must be a non-zero TimeSpan.", nameof(TokenProviderOptions.Expiration));
}
if (options.IdentityResolver == null)
{
throw new ArgumentNullException(nameof(TokenProviderOptions.IdentityResolver));
}
if (options.SigningCredentials == null)
{
throw new ArgumentNullException(nameof(TokenProviderOptions.SigningCredentials));
}
if (options.NonceGenerator == null)
{
throw new ArgumentNullException(nameof(TokenProviderOptions.NonceGenerator));
}
}
private TokenData GetHeaderContext(HttpContext context)
{
string token = new StreamReader(context.Request.Body).ReadToEnd();
if (string.IsNullOrEmpty(token))
{
return null;
}
var encrypted = Convert.FromBase64String(token);
var decriptedFromJavascript = Mapurija.Services.Common.TokenDecrypter.DecryptStringFromBytes(encrypted, Mapurija.Services.Common.TokenDecrypter.KeyBytes, Mapurija.Services.Common.TokenDecrypter.Vi);
TokenData result = JsonConvert.DeserializeObject< TokenData>(decriptedFromJavascript);
return result;
}
/// <summary>
/// Get this datetime as a Unix epoch timestamp (seconds since Jan 1, 1970, midnight UTC).
/// </summary>
/// <param name="date">The date to convert.</param>
/// <returns>Seconds since Unix epoch.</returns>
public static long ToUnixEpochDate(DateTime date) => (long)Math.Round((date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds);
}
public class TokenProviderOptions
{
/// <summary>
/// The relative request path to listen on.
/// </summary>
/// <remarks>The default path is <c>/token</c>.</remarks>
public string Path { get; set; } = "api/token";
/// <summary>
/// The Issuer (iss) claim for generated tokens.
/// </summary>
public string Issuer { get; set; }
/// <summary>
/// The Audience (aud) claim for the generated tokens.
/// </summary>
public string Audience { get; set; }
/// <summary>
/// The expiration time for the generated tokens.
/// </summary>
/// <remarks>The default is five minutes (300 seconds).</remarks>
public TimeSpan Expiration { get; set; } = TimeSpan.FromDays(365);
/// <summary>
/// The signing key to use when generating tokens.
/// </summary>
public SigningCredentials SigningCredentials { get; set; }
/// <summary>
/// Resolves a user identity given a username and password.
/// </summary>
public Func<string, Task<ClaimsIdentity>> IdentityResolver { get; set; }
/// <summary>
/// Generates a random value (nonce) for each generated token.
/// </summary>
/// <remarks>The default nonce is a random GUID.</remarks>
public Func<Task<string>> NonceGenerator { get; set; } = new Func<Task<string>>(() => Task.FromResult(Guid.NewGuid().ToString()));

Error with Ninject MVC 2 extension - CreateKernel()

I have the following in my Global.asax.cs:
using System.Web.Mvc;
using System.Web.Routing;
using HandiGamer.WebUI.ConditionalValidation;
using HandiGamer.Domain.Concrete;
using Ninject;
using Ninject.Web.Common;
using System.Reflection;
using HandiGamer.Domain.Abstract;
//namespace....
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("favicon.ico");
routes.MapRoute(
null,
"Reviews/{id}",
new { controller = "Reviews", action = "ShowReviewById" },
new { id = #"\d+" }
);
routes.MapRoute(
null,
"Reviews/Latest",
new { controller = "Reviews", action = "Latest"}
);
routes.MapRoute(
null,
"Reviews/{slug}",
new { controller = "Reviews", action = "ShowReviewBySlug" },
new { slug = #"[0-9a-zA-Z\-]+" }
);
routes.MapRoute(
null,
"News/{id}",
new { controller = "News", action = "ShowNewsById" },
new { id = #"\d+" }
);
routes.MapRoute(
null,
"News/{slug}",
new { controller = "News", action = "ShowNewsBySlug" },
new { slug = #"[0-9a-zA-Z\-]" }
);
routes.MapRoute(
null,
"Articles/{id}",
new { controller = "Articles", action = "ShowArticleById" },
new { id = #"\d+" }
);
routes.MapRoute(
null,
"Articles/{slug}",
new { controller = "Articles", action = "ShowArticleBySlug" },
new { slug = #"[0-9a-zA-Z\-]" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
routes.MapRoute(
null,
"{*path}",
new { controller = "Error", action = "Http404" }
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute), typeof(RequiredIfValidator));
}
protected override IKernel CreateKernel()
{
var kernel = new StandardKernel();
kernel.Bind<IArticleRepository>().To<HGArticleRepository>();
kernel.Bind<IGameRepository>().To<HGGameRepository>();
kernel.Bind<INewsRepository>().To<HGNewsRepository>();
kernel.Load(Assembly.GetExecutingAssembly());
return kernel;
}
}
But upon building my solution, I get the following error:
Error 1 'HandiGamer.MvcApplication.CreateKernel()': no suitable method found to override C:\Users\Kevin\documents\visual studio 2010\Projects\HandiGamer\HandiGamer\Global.asax.cs 92 36 HandiGamer.WebUI
Any ideas? I've pretty much copied what was in the github sample app, so I'm stumped.
You have to derive from NinjectHttpAplication

Resources