Error coming string reference not set to an instance of a String. Parameter name: s - asp.net-core-mvc

I am working on authentication and authorization in JWT. But I have an error coming which do not not why.
builder.Services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(builder.Configuration.GetSection("Jwt:Key").Value)),
ValidateIssuer = false,
ValidateAudience = false
};
});

I add code like below in appsettings.json :
"JWT": {
"Key": "fc746b61cde4f6665d3f9791446cd5395661860c0075a905ed9810b7391af467",
"Issuer": "Comply",
"Audience": "comply"
}
In Program.cs:
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JWT:Key"]))

Related

ASP.NET Web API work cors with identity server 4

I try to connect the ASP.NET Web API (not .NET Core) with identity server.
I use owin as startup but I get a cors error.
Why does cors
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
not work with UseOpenIdConnectAuthentication? It works when I remove it
public class Startup1
{
public void Configuration(IAppBuilder app)
{
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = "insig_spa",
Authority = "https://localhost:5000",
RedirectUri = "https://localhost:5002/auth-callback",
Scope = "openid profile email insigapi.read",
SignInAsAuthenticationType = "cookie",
RequireHttpsMetadata = false,
UseTokenLifetime = false,
RedeemCode = true,
SaveTokens = true,
ClientSecret = "secret",
ResponseType = OpenIdConnectResponseType.Code,
ResponseMode = "query",
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
{
// set PKCE parameters
var codeVerifier = CryptoRandom.CreateUniqueId(32);
string codeChallenge;
using (var sha256 = SHA256.Create())
{
var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier));
codeChallenge = Base64Url.Encode(challengeBytes);
}
n.ProtocolMessage.SetParameter("code_challenge", codeChallenge);
n.ProtocolMessage.SetParameter("code_challenge_method", "S256");
// remember code_verifier (adapted from OWIN nonce cookie)
RememberCodeVerifier(n, codeVerifier);
}
return Task.CompletedTask;
},
AuthorizationCodeReceived = n =>
{
// get code_verifier
var codeVerifier = RetrieveCodeVerifier(n);
// attach code_verifier
n.TokenEndpointRequest.SetParameter("code_verifier", codeVerifier);
return Task.CompletedTask;
}
}
});
}
}

IdentityServer 4 - Adding a custom claim to a User when using Implicit grant type

I'm going through the tutorial of IdentityServer 4 where is explained how to add user authentication with OpenID Connect, it can be found here:
http://docs.identityserver.io/en/latest/quickstarts/3_interactive_login.html
Basically in this tutorial we have an MVC application with a controller action decorated with a Authorized attribute.
Each time a user tries to access that action, in case he/she is not logged in, the MVC application redirects the user to Identity Server so he/she can input the login credentials.
If the credentials are correct, Identity Server redirects back to the MVC application where a page with the User's credentials is shown.
I've concluded the tutorial and now I want to explore a bit more by adding new claims to the token but I haven't been successful so far.
In the tutorial, the scopes OpenId and Profile are added by setting the AllowedScopes on the Client configuration.
I tried to do create a "age" scope and add it in the same manner, but it didn't work.
Does anyone have an idea how I can do this?
The code is shown bellow (commented lines are stuff I already tried).
IdentityServer setup:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// configure identity server with in-memory stores, keys, clients and scopes
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryClients(Config.GetClients())
.AddTestUsers(Config.GetUsers());
}
In memory stores config:
public class Config
{
public static IEnumerable<Client> GetClients()
{
return new List<Client>()
{
new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.Implicit,
// where to redirect to after login
RedirectUris = { "http://localhost:5002/signin-oidc" },
// where to redirect to after logout
PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },
RequireConsent = false,
//AlwaysIncludeUserClaimsInIdToken = true,
//AlwaysSendClientClaims = true,
//AllowAccessTokensViaBrowser = true,
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"age"
}
}
};
}
public static List<TestUser> GetUsers()
{
return new List<TestUser>()
{
new TestUser()
{
SubjectId = "1",
Username = "alice",
Password = "password",
Claims = new List<Claim>()
{
new Claim("age", "15"),
new Claim("name", "Alice"),
new Claim("website", "https://alice.com")
}
},
new TestUser()
{
SubjectId = "2",
Username = "bob",
Password = "password",
Claims = new List<Claim>()
{
new Claim("age", "16"),
new Claim("name", "Bob"),
new Claim("website", "https://bob.com")
}
}
};
}
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource()
{
DisplayName = "Age",
Name = "age",
UserClaims = new List<string>()
{
"age"
}
}
};
}
}
MVC Application Config:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore()
.AddRazorViewEngine()
.AddAuthorization()
.AddJsonFormatters();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.SaveTokens = true;
});
}
MVC Application Claims Page:
<dl>
#foreach (var claim in User.Claims)
{
<dt>#claim.Type</dt>
<dd>#claim.Value</dd>
}
</dl>
This is the result after a successful login:
sid
ba7ecb47f66524acce04e321b8d2c444
sub
2
idp
local
name
Bob
website
https://bob.com
As you can see the profile claims (name and website) show up, but the custom "age" claim does not.
The answer to the original question is to explicitly add which claims we want to use when setting up OpenId Connect.
We neet to add the following lines inside the .AddOpenIdConnect method:
options.Scope.Clear();
options.Scope.Add("age");
The complete Setup is shown below:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore()
.AddRazorViewEngine()
.AddAuthorization()
.AddJsonFormatters();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.SaveTokens = true;
options.Scope.Clear();
options.Scope.Add("age");
//Add all the claims you need
});
}

invalid_grant error IdentityServer 3 & asp.net core

I have an ASP.NET Core 2 MVC app using identity server 3 with Hybrid flow with an intention of fetching access tokens also which i can use further for accessing API's, sometimes I am redirected to the IDP login page and after entering username and password i am redirected back to the MVC app, but it is failing randomly.
I have the following configuration
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddOpenIdConnect(options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = authConfig.GetValue<string>("Authority");
options.RequireHttpsMetadata = false;
options.ClientId = authConfig.GetValue<string>("ClientId");
options.ClientSecret = authConfig.GetValue<string>("ClientSecret");
options.ResponseType = "code id_token token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = false;
options.TokenValidationParameters = new
TokenValidationParameters
{
NameClaimType = ClaimTypes.Name,
RoleClaimType = ClaimTypes.Role
};
options.Events = new OpenIdConnectEvents
{
OnRemoteFailure = context =>
{
context.HttpContext.Response.Redirect($"/Error?RequestId=4000&errormessage={context.Failure?.Message }");
context.HandleResponse();
return Task.FromResult(0);
},
OnRedirectToIdentityProvider = context =>
{
//TODO: Get IdentityProvider value for Multiple subscribers and not from config
var idp = authConfig.GetValue<string>("IdentityProvider");
var acrValues = new List<string>();
if (!string.IsNullOrWhiteSpace(idp))
acrValues.Add($"idp:{idp}");
if (acrValues.Count > 0)
context.ProtocolMessage.AcrValues = string.Join(" ", acrValues);
//if (context.ProtocolMessage.RequestType != OpenIdConnectRequestType.Logout)
//{
// if (!CurrentEnvironment.IsDevelopment() &&
// context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
// {
// // in widget iframe skip prompt login screen
// context.ProtocolMessage.Prompt = "none";
// }
// return Task.FromResult(0);
//}
var idTokenHint = context.HttpContext.User.FindFirst("id_token");
if (idTokenHint != null)
context.ProtocolMessage.IdTokenHint = idTokenHint.Value;
return Task.FromResult(0);
}
and the configuration on Identity server for the client is like
"ClientName": "SampleApp",
"ClientId": "sample.app.mvc",
"Flow": 2,
"RedirectUris": ["https://localhost:44368/signin-oidc"],
"PostLogoutRedirectUris": ["https://localhost:44368/"],
"PrefixClientClaims": true,
"RequireConsent": false,
"AllowedScopes":
[
"openid",
"profile",
"roles",
"CustomScope"
],
"Claims": [{
"Type": "subscriberId",
"Value": "dcbe85c6-05b6-470d-b558-289d1ae3bb15"
}],
"ClientSecrets": [{
"Secret": "tudc73K2y7pnEjT2"
}],
"IdentityTokenLifetime": 300,
"AccessTokenLifetime": 3600,
"AuthorizationCodeLifetime": 300,
"EnableLocalLogin": true
}
I keep hitting the error invalid_grant most of the times when i try in browsers. Can you please tell me what part of the configuration is incorrect?
I have found what the issue is here.
It wasn't happening because of any configuration mentioned above. But because of the fact that I was using InMemory store for AuthorizationCode store, and also my identity server was deployed on Azure and had 2 instances.

Identityserver3 error Unable to get document from: https://localhost:44300/identity/.well-known/openid-configuration

I have created an identityserver to issue token. Using identityserver3 to setup.
I am using a local .pfx certificate with password for signing the tokens.
this was working good but not sure why i am getting the following error as shown in the attachment.
Really making me crazy.
Below is the code in startup.cs on the authorization server. The certificate file is located in \bin\debug folder
public class X509Certificate2Wrapper : IX509Certificate2Wrapper
{
public X509Certificate2 LoadCertificate(string filename, string password)
{
var path = $#"{AppDomain.CurrentDomain.BaseDirectory}{filename}";
return new X509Certificate2(path, password);
}
}
app.Map("/identity", idsrvApp =>
{
idsrvApp.UseIdentityServer(new IdentityServerOptions
{
SiteName = "Identity Manager",
IssuerUri = Common.Constants.IdSrvIssuerUri,
SigningCertificate = X509Certificate2Wrapper.LoadCertificate(CertificateFilename, CertificatePassword),
Factory = factory,
RequireSsl = true,
EnableWelcomePage = false,
AuthenticationOptions = new AuthenticationOptions
{
EnableSignOutPrompt = false,
EnablePostSignOutAutoRedirect = true,
PostSignOutAutoRedirectDelay = 3,
CookieOptions = new IdentityServer3.Core.Configuration.CookieOptions
{
ExpireTimeSpan = new TimeSpan(0, IdentityServerServices.AuthenticationTimeout(), 0),
SlidingExpiration = true
},
//
// Note: Uncomment following line to enable WindowsAuthentication only - logout related settings will also require removal!
//
//EnableLocalLogin = false,
IdentityProviders = ConfigureIdentityProviders
},
Endpoints = new EndpointOptions
{
EnableAccessTokenValidationEndpoint = true,
EnableAuthorizeEndpoint = true,
EnableCheckSessionEndpoint = false,
EnableClientPermissionsEndpoint = false,
EnableCspReportEndpoint = false,
EnableDiscoveryEndpoint = true,
EnableEndSessionEndpoint = true,
EnableIdentityTokenValidationEndpoint = true,
EnableIntrospectionEndpoint = false,
EnableTokenEndpoint = true,
EnableTokenRevocationEndpoint = false,
EnableUserInfoEndpoint = true
}
});
});
Not sure this is the perfect answer. But i did the following and all working fine for me.
i) followed steps regarding certificatesas mentioned in https://github.com/IdentityServer/IdentityServer3.Samples/tree/master/source/Certificates
ii) To resolve this I moved the "localhost" IIS Express Cert from the Personal CertStore to the Trusted Root Certification Authorities and the issue was gone.

Custom statusText for JWT Token MVC Core

I have implemented JWT Token authentication in my MVC Core Application using the following articles:
Link 1
Link 2
This is what I have in my Startup.cs
private const string SecretKey = "MySecretKey"; //TODO: remove hard coded get from environments as suggested in Blog
private readonly SymmetricSecurityKey _signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey));
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)],
ValidateAudience = true,
ValidAudience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)],
ValidateIssuerSigningKey = true,
IssuerSigningKey = _signingKey,
RequireExpirationTime = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = tokenValidationParameters,
});
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "areaRoute",
template: "{area:exists}/{controller}/{action}",
defaults: new { controller = "Home", action = "Index" }
);
});
}
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc(options => { });
services.Configure<MvcOptions>(options => { });
services.Configure<IISOptions>(options => { });
services.AddOptions();
#region Authentication and Authorisation
services.AddAuthorization(options =>
{
using (var dbContext = new FoodHouseContext(Configuration.GetConnectionString("DefaultConnection")))
{
var features = dbContext.Features.Select(s => s.Name).ToList();
foreach (var feature in features)
{
options.AddPolicy(feature, policy => policy.Requirements.Add(new FeatureRequirement(feature)));
}
}
});
// Configure JWT Token Settings
var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));
services.Configure<JwtIssuerOptions>(options =>
{
options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
options.Audience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)];
options.SigningCredentials = new SigningCredentials(_signingKey, SecurityAlgorithms.HmacSha256);
});
#endregion
}
Everything is working fine - the Token is been issued and Authorize is working fine, when I make an UnAuthenticated request I get the following response:
As you can see the Status = 401, which means UnAuthenticated - But as you can see the StatusText is empty.
Now there are many reasons why the request can be UnAuthenticated e.g. JWT Token expired , what I want to do is pass a custom Status Text based on the Validation rule failing on the server, how can I do this in MVC Core?

Resources