i have a custom cors policy like below, where I am setting support-credentials to false
public class CorsProviderFactory : ICorsPolicyProviderFactory
{
//https://msdn.microsoft.com/en-us/magazine/dn532203.aspx
public ICorsPolicyProvider GetCorsPolicyProvider(
HttpRequestMessage request)
{
return new CorsPolicyProviderCustom();
}
public class CorsPolicyProviderCustom : Attribute, ICorsPolicyProvider
{
private readonly CorsPolicy _policy;
public CorsPolicyProviderCustom()
{
// Create a CORS policy.
_policy = new CorsPolicy
{
AllowAnyMethod = true,
AllowAnyHeader = true,
AllowAnyOrigin = true,
SupportsCredentials = false
};
// Magic line right here
_policy.Origins.Add("*");
_policy.Methods.Add("*");
}
public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult(_policy);
}
}
}
and used it like :
public static HttpConfiguration Register()
{
var config = new HttpConfiguration();
config.SetCorsPolicyProviderFactory(new CorsProviderFactory());
config.EnableCors();
.................
}
but even then in the postman response i see, support-credentials as true
how can I get support-credentials as false, the breakpoint does reaches to the custom policy part, so why is it that its not working :(
For security reasons you can not use Access-Control-Allow-Credentails with Access-Control-Allow-Origin set to *.
You must specify the exact domain(s) in Access-Control-Allow-Origin, OR set Access-Control-Allow-Credentails to false.
Related
Questions
First question, what determines if an sid claim is emitted from identityserver?
Second question, do I even need an sid? I currently have it included because it was in the sample..
Backstory
I have one website that uses IdentityServer4 for authentication and one website that doesn't. I've cobbled together a solution that allows a user to log into the non-identityserver4 site and click a link that uses one-time-access codes to automatically log into the identityserver4 site. Everything appears to work except the sid claim isn't passed along from identityserver to the site secured by identityserver when transiting from the non-identityserver site. If I log directly into the identityserver4 secured site the sid is included in the claims. Code is adapted from examples of automatically logging in after registration and/or impersonation work flows.
Here is the code:
One time code login process in identityserver4
public class CustomAuthorizeInteractionResponseGenerator : AuthorizeInteractionResponseGenerator
{
...
//https://stackoverflow.com/a/51466043/391994
public override async Task<InteractionResponse> ProcessInteractionAsync(ValidatedAuthorizeRequest request,
ConsentResponse consent = null)
{
string oneTimeAccessToken = request.GetAcrValues().FirstOrDefault(x => x.Split(':')[0] == "otac");
string clientId = request.ClientId;
//handle auto login handoff
if (!string.IsNullOrWhiteSpace(oneTimeAccessToken))
{
//https://benfoster.io/blog/identity-server-post-registration-sign-in/
oneTimeAccessToken = oneTimeAccessToken.Split(':')[1];
OneTimeCodeContract details = await GetOTACFromDatabase(oneTimeAccessToken);
if (details.IsValid)
{
UserFormContract user = await GetPersonUserFromDatabase(details.PersonId);
if (user != null)
{
string subjectId = await GetClientSubjectIdAsync(clientId, user.AdUsername);
var iduser = new IdentityServerUser(subjectId)
{
DisplayName = user.AdUsername,
AuthenticationTime = DateTime.Now,
IdentityProvider = "local",
};
request.Subject = iduser.CreatePrincipal();
//revoke token
bool? success = await InvalidateTokenInDatabase(oneTimeAccessToken);
if (success.HasValue && !success.Value)
{
Log.Debug($"Revoke failed for {oneTimeAccessToken} it should expire at {details.ExpirationDate}");
}
//https://stackoverflow.com/a/56237859/391994
//sign them in
await _httpContextAccessor.HttpContext.SignInAsync(IdentityServerConstants.DefaultCookieAuthenticationScheme, request.Subject, null);
return new InteractionResponse
{
IsLogin = false,
IsConsent = false,
};
}
}
}
return await base.ProcessInteractionAsync(request, consent);
}
}
Normal Login flow when logging directly into identityserver4 secured site (from sample)
public class AccountController : Controller
{
/// <summary>
/// Handle postback from username/password login
/// </summary>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginInputModel model)
{
Log.Information($"login request from: {Request.HttpContext.Connection.RemoteIpAddress.ToString()}");
if (ModelState.IsValid)
{
// validate username/password against in-memory store
if (await _userRepository.ValidateCredentialsAsync(model.Username, model.Password))
{
AuthenticationProperties props = null;
// only set explicit expiration here if persistent.
// otherwise we reply upon expiration configured in cookie middleware.
if (AccountOptions.AllowRememberLogin && model.RememberLogin)
{
props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
};
};
var clientId = await _account.GetClientIdAsync(model.ReturnUrl);
// issue authentication cookie with subject ID and username
var user = await _userRepository.FindByUsernameAsync(model.Username, clientId);
var iduser = new IdentityServerUser(user.SubjectId)
{
DisplayName = user.UserName
};
await HttpContext.SignInAsync(iduser, props);
// make sure the returnUrl is still valid, and if yes - redirect back to authorize endpoint
if (_interaction.IsValidReturnUrl(model.ReturnUrl))
{
return Redirect(model.ReturnUrl);
}
return Redirect("~/");
}
ModelState.AddModelError("", AccountOptions.InvalidCredentialsErrorMessage);
}
// something went wrong, show form with error
var vm = await _account.BuildLoginViewModelAsync(model);
return View(vm);
}
}
AuthorizationCodeReceived in identityserver4 secured site
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
// use the code to get the access and refresh token
var tokenClient = new TokenClient(
tokenEndpoint,
electionClientId,
electionClientSecret);
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(
n.Code, n.RedirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
// use the access token to retrieve claims from userinfo
var userInfoClient = new UserInfoClient(
new Uri(userInfoEndpoint).ToString());
var userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);
Claim subject = userInfoResponse.Claims.Where(x => x.Type == "sub").FirstOrDefault();
// create new identity
var id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
id.AddClaims(GetRoles(subject.Value, tokenClient, apiResourceScope, apiBasePath));
var transformedClaims = StartupHelper.TransformClaims(userInfoResponse.Claims);
id.AddClaims(transformedClaims);
id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
THIS FAILS -> id.AddClaim(new Claim("sid", n.AuthenticationTicket.Identity.FindFirst("sid").Value));
n.AuthenticationTicket = new AuthenticationTicket(
new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name", "role"),
n.AuthenticationTicket.Properties);
},
}
});
}
}
Questions again if you don't want to scroll back up
First question, what determines if an sid claim is emitted from identityserver?
Second question, do I even need an sid? I currently have it included because it was in the sample..
I have an Angular5 APP with .NET Core WebApi and IdentityServer.
After the build and deploy, I am seeing this
Unauthorized_Client
"Invalid Redirect Uri".
I am currently stuck on fixing this one.
These are the steps taken:
This redirect Uri is set as: http://localhost:4200/signin-oidc
I am sure that anything related to RedirectUri are matching in value
(1) in the Database
IdentityServerData. ClientRedirectUri, column RedirectUri
(2) in the security project > IdentityServer.webapi
public static class ConfigurationDbContextSeederExtensions
{
#region Public Static Methods
/// <summary>
/// Seeds the identity server data tables to make sure they always have data.
/// </summary>
/// <param name="context"></param>
public static void EnsureSeedDataForContext(this ConfigurationDbContext context)
{
new Client
{
ClientId = "myapp-client",
ClientName = "My App",
AccessTokenType = AccessTokenType.Reference,
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RequireConsent = false,
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"myapp-api"
},
RedirectUris = new List<string> {"http://localhost:4200/signin-oidc"},
PostLogoutRedirectUris = new List<string> { "http://localhost:4200" }
}
};
(3) and in the environment.prod.ts
openIdConnectSettings: {
authority: 'http://localhost:61173/',
client_id: 'myapp-ng',
redirect_uri: 'http://localhost:4200/signin-oidc',
scope: 'openid email profile myapp-api',
response_type: 'id_token token',
post_logout_redirect_uri: 'http://localhost:4200',
automaticSilentRenew: true,
silent_redirect_uri: 'http://localhost:4200/redirect-silentrenew'
}
In an Asp net core MVC application, I use Active Directory for automatic login like this :
this.user = UserPrincipal.FindByIdentity(this.context, Environment.UserName);
and I get groups of the user with this :
public List<String> GetUserGroups()
{
List<String> groups = new List<String>();
foreach(GroupPrincipal gr in user.GetGroups())
{
groups.Add(gr.Name);
}
return groups;
}
And I would like to implement Autorisation with this groups, something like that :
[Authorize(Roles ="Admin")]
public IActionResult OnlyAdmin(){}
with something that link AD groups with authorization Roles or directly check authorization with AD groups if possible but I don't know how to do something like that.
note : I haven't any login/logout pages, it's only automatic.
EDIT
Don't know exactly why or how but it finaly work whithout any code and only with the user login in the PC not the user specified in this.user but it's fine like that.
But now I get a 404 error when I'm trying to access a denied page, why it's not a 401 or 403 error ? How can I redirect a denied access to a custom error page ?
You need to add the group in the ClaimsPrincipal class, i.e.
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Name, username));
foreach (string userGroup in authResponse)
{
claims.Add(new Claim(ClaimTypes.Role, userGroup, ClaimValueTypes.String,"system","system"));
}
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, "authenticationScheme"));
Now use authorize attribute, either on controller or action as :
[Authorize(Roles = "guest,home")]
You can write an ErrorHandlingMiddleware as follows. You will need to register it in the startup file
app.UseMiddleware(typeof(ErrorHandlingMiddleware));
following is an example for the same.
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public ErrorHandlingMiddleware(RequestDelegate next, ILogger<ErrorHandlingMiddleware> createLogger)
{
this._next = next;
this._logger = createLogger;
}
public async Task Invoke(HttpContext context)
{
var statusCode = HttpStatusCode.OK;
try
{
await _next.Invoke(context);
}
catch (Exception ex)
{
this._logger.LogError(ex, ex.Message);
switch (context.Response.StatusCode)
{
case (int)HttpStatusCode.NotFound:
statusCode = HttpStatusCode.NotFound;
break;
case (int)HttpStatusCode.Forbidden:
statusCode = HttpStatusCode.Forbidden;
break;
case (int)HttpStatusCode.BadRequest:
statusCode = HttpStatusCode.BadRequest;
break;
default:
statusCode = HttpStatusCode.InternalServerError;
break;
}
context.Response.StatusCode = (int)statusCode;
}
if (!context.Response.HasStarted)
{
context.Response.ContentType = "application/json";
var response = new { code = statusCode };
var json = JsonConvert.SerializeObject(response);
await context.Response.WriteAsync(json);
}
}
}
I'm trying to get a self-hosted Nancy app running, but I'm having trouble getting it to return valid responses. I'm new at Nancy; I expect my problem is something fairly simple.
Here's some code:
class Program
{
static void Main(string[] args)
{
const String PORT_SETTING = "webServicePortNumber";
const String URI = "http://localhost:{0}/download/";
var portNum = ConfigurationManager.AppSettings[PORT_SETTING];
var uri = new Uri(String.Format(URI, portNum));
var config = new HostConfiguration {
UrlReservations = new UrlReservations { CreateAutomatically = true }
};
using (var nancyHost = new NancyHost(new Bootstrapper(), config, uri)) {
nancyHost.Start();
Console.WriteLine(String.Format("Listening on {0}. Press any key to stop.", uri.AbsoluteUri));
Console.ReadKey();
}
Console.WriteLine("Stopped. Press any key to exit.");
Console.ReadKey();
}
}
internal class Bootstrapper : DefaultNancyBootstrapper
{
protected override Nancy.Diagnostics.DiagnosticsConfiguration DiagnosticsConfiguration
{
get {
return new DiagnosticsConfiguration {
Password = #"[password]"
};
}
}
}
My NancyModule looks like this:
public class DownloadsModule : NancyModule
{
public DownloadsModule() : base("/download")
{
RegisterRoutes();
}
private void RegisterRoutes()
{
Put["/"] = parms => InitiateDownload(parms);
Get["/"] = parms => Summary(parms);
Get["/{id}"] = parms => GetStatus(parms.requestId);
}
private Response GetStatus(Guid requestId)
{
return Response.AsText("TEST: GetStatus requestId " + requestId);
}
private Response Summary(dynamic parms)
{
return Response.AsText("Summary: You loved me before, do you love me now?");
}
private Response InitiateDownload(dynamic parms)
{
return Response.AsText("InitiateDownload.");
}
}
Nancy is running; I can access the diagnostics at http://127.0.0.1:8880/download/_Nancy/. Looking at them, the routes appear ready. Interactive Diagnostics/GetAllRoutes shows:
P U T
name: [nothing] path: /download
G E T
name: [nothing] path: /download
name: [nothing] path: /download/{id}
And yet, I'm getting 404s back when I try http://localhost:8880/download/.
The request trace on the diagnostics page shows:
Method: GET
Request Url:
Scheme: http
Host Name: localhost
Port: 8880
Base Path: /download
Path: /
Query:
Site Base: http://localhost:8880
Is Secure: false
Request Content Type:
Response Content Type: text/html
Request Headers:
<snip>
Accept: text/html;q=1
application/xhtml+xml;q=1
image/webp;q=1
application/xml;q=0.9
*/*;q=0.8
<snip>
Response Headers:
Status Code: 404
Log: New Request Started
[DefaultResponseNegotiator] Processing as real response
So why isn't Nancy routing this request to the proper route?
Problem pointed out to me by jchannon in the Nancy JabbR room:
The URI specifies http://localhost:{0}/download/, while the module also specifies a base path of /download, so currently its looking for an URL of http://localhost:{0}/download/download/
I have a simple method defined in my Rest Resource as below:
#RequestMapping(value = "/{studyId}/cases/{caseId}/exportlocation/{exportLocation}", method = RequestMethod.PUT)
#Timed
public void exportCase(#PathVariable Long studyId, #PathVariable Long caseId, #PathVariable String exportLocation,
#RequestBody Case acase) throws Exception {
log.debug("REST request to export Case {} for Study : {}", acase, studyId);
String exportFileName = exportService.exportCase(acase, "test");
// if (exportFileName == null) {
// response.sendError(HttpServletResponse.SC_NOT_FOUND, "Can't Export");
// }
// return exportFileName;
}
When I make a call on the page, I can see the URL as being /app/rest/studies/1/cases/1/exportlocation/test
I have the Request Mapping defined as
#RequestMapping(value = StudyResource.REQUEST_MAPPING, produces = MediaType.APPLICATION_JSON_VALUE)
#Secured(AuthoritiesConstants.USER)
public class StudyResource {
private final Logger log = LoggerFactory.getLogger(StudyResource.class);
public static final String REQUEST_MAPPING = "/app/rest/studies";
But keep getting a 415 Unsupported Media type. Can someone please look at the lines of code and tell me what is wrong. I highly appreciate your time and help.
My JS layer from where the calls are made on the page are as shown"
$scope.exportCase = function(studyId, caseId, exportLocation){
StudyService.updatecase.get({studyId:studyId,caseId:caseId}).$promise.then(function(acase){
$scope.acase = acase;
console.log(acase);
});
StudyService.exportcase.exportc({studyId: studyId,caseId:caseId,exportLocation:exportLocation},$scope.acase,
function () {
AND JS Service part below
exportcase : $resource('app/rest/studies/:studyId/cases/:caseId/exportlocation/:exportLocation', {}, {
'exportc' : {
method : 'PUT',
params : {
studyId : '#studyId',
caseId : '#caseId',
exportLocation : '#exportLocation'
}
},
})