I'm using Owin WsFederation authentication. For unauthorized users I want one path to be redirected to STS and another to return 401 response. Is it possible to set different AuthenticationMode for different path?
You can "fork" the OWIN pipeline in order to configure middleware differently for different paths.
public void Configuration(IAppBuilder app)
{
app.UseErrorPage(new ErrorPageOptions());
app.Map("active", active =>
{
active.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active
//TODO: Add other options.
});
});
app.Map("passive", passive =>
{
passive.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Passive,
//TODO: Add other options.
});
});
}
This sample configures the "error page" middleware to run on all requests, then goes on to create two URL-mapped pipelines, one for requests which start /active and the other for paths which start /passive. Each of these mapped pipelines partially configure an OIDC authentication middleware, using the active and passive modes respectively.
This mechanism is designed to let you run different sets of middleware when requests come in on specific paths. Put common middleware before the mapped middleware to make it run on every request.
Not that I know of - but you can add more than one instance of the middleware in the pipeline, using different AuthenticationType, and use them on the different routes.
Related
Using:
template Angular + ASP.NET Core application from VS Studio 2019
Angular 8.2.12
.NET 5
MySQL backend
Problem:
Project implements IdentityServer4 for Authentication/Authorization but when adding a certificate to the IDS4 instance Authorization fails as context.User returns null.
Startup.cs:
call the certificate from certificate store:
X509Certificate2 cert = null;
using (var certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
certStore.Open(OpenFlags.ReadOnly);
// var certCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint,"THUMBPRINT>, false);
var certCollection = certStore.Certificates.Find(X509FindType.FindBySubjectName,"localhost", false);
if (certCollection.Count > 0)
cert = certCollection[0];
}
set the certificate on IdentityServer4:
if(cert == null)
{
services.AddIdentityServer()
//.AddDeveloperSigningCredential()
.AddSigningCredential(cert)
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
}
else
{
services.AddIdentityServer()
.AddSigningCredential(cert)
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
}
adding Authentication & Authorization:
services.AddAuthentication()
.AddIdentityServerJwt();
services.AddScoped<IAuthorizationHandler, UserAuthorizationHandler>();
services.AddAuthorization(options =>
{
options.AddPolicy("default", policy =>
{
policy.RequireAuthenticatedUser();
//To require the basic user_impersonation scope across the API, you can use:
//policy.RequirePermissions(
// delegated: new[] { "user_impersonation" },
// application: new string[0]);
});
options.AddPolicy("LocalAuthorizationPolicy", policy => policy.Requirements.Add(new UserRequirement(CustomRoleTypes.SiteAdmin)));
});
The certificate gets correctly found and assigned to the IDS4 instance.
User Authentication proceeds correctly and user gets redirected to the correct location.
User is able to call General -non authorized- Controller methods.
When controller method is decorated by [Authorize] the controller returns a 401 error.
Because the user is not recognized within the context and returns null.
(i.e. AuthorizationHandlerContext as well as IHttpContextAccessor return null for ClaimsPrincipal)
The latter does not happen when IDS4 is instantiated using no SigningCredentials.
i.e. when using:
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
it all works correctly...
I have checked the returned user using the LocalAuthorizationPolicy and determine if there is a user identified in the request context.
I have searched many sites for a walkthrough on the ideal implementation but so far have not been able to find the solution.
I noticed that there are several posts that recommend installing IdentityServer as its own project . But haven't been able to implement that yet...
Any advise would be appreciated.
I'm working on an ASP.Net project, which needs to be deployed after completion on PaaS, which needs to be BlueMix (It wasn't my choice, It was an order).
In addition I need to use an:
Active Directory or LDAP to the User Authentication and Authorization, integrated with the ASP.Net Project.
The Issues Here Are :
1. I have found an integration to the Active Directory or SSO Services using only Java or Node.js, but in my case I am using ASP.Net
2. I want a solution for how the integration can be done on top of the PaaS between the Active Directory and ASP.Net application.
Depending on which version of ADFS you're using, you should be able to use either OAuth or OIDC middleware to connect from an ASP.NET Core application (assuming you're using ASP.NET Core because you're using Bluemix). If you're using at least ADFS 3.0 (Windows Server 2012+), you can use ASP.NET Core's generic OAuth middleware to connect.
First, create a configuration file to store your ADFS server settings, or modify an existing configuration file (such as appsettings.json).
Sample "adfs-settings.json" file:
{
"ADFS": {
"ClientId": "Your ClientId as set on ADFS server",
"ResourceUrl": "url of this application (ex: http://mywebapp.mybluemix.net)",
"ServerUrl": "url of ADFS (ex: https://dc.your.domain)"
}
}
If you created a new file, such as "adfs-settings.json", for your ADFS configuration, add it to your Configuration object in the constructor of your Startup.cs file.
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("adfs-settings.json");
Configuration = builder.Build();
}
In your Configure method of Startup.cs create an OAuthOptions object:
var options = new OAuthOptions();
options.AutomaticChallenge = true;
options.AuthenticationScheme = "ADFS";
Specify the ClientId that you created when configuring this application on your ADFS server by reading it from your Configuration object. The generic OAuth middleware also requires that you provide a ClientSecret here even though that value is not actually used by ADFS 3.0.
options.ClientId = Configuration["ADFS:ClientId"];
options.ClientSecret = "ADFS 3.0 does not support confidential client, but OAuth middleware requires it";
Set the callback url which the ADFS server will redirect to in your application.
options.CallbackPath = new PathString("/signin-adfs");
Now configure the OAuthEvents. OnRedirectToAuthorizationEndpoint defines parameters which are passed to the ADFS authorization endpoint when the application determines that a user needs to be authorized. This will require at least a resource parameter which points to the url of your application. OnCreatingTicket is triggered when the ADFS server has finished authorizing a client and returns a JWT token containing claims data to your application. In this method you'll need to process adding roles and claims to the HttpContext object.
options.Events = new OAuthEvents {
OnRedirectToAuthorizationEndpoint = context =>
{
var parameter = new Dictionary<string, string>
{
["resource"] = Configuration["ADFS:ResourceUrl"]
};
var query = QueryHelpers.AddQueryString(context.RedirectUri, parameter);
context.Response.Redirect(query);
return Task.CompletedTask;
},
OnCreatingTicket = context => {
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
JwtSecurityToken validatedToken = tokenHandler.ReadJwtToken(context.AccessToken);
IEnumerable<Claim> a = validatedToken.Claims;
foreach (var claim in a)
{
// role claim needs to be mapped to http://schemas.microsoft.com/ws/2008/06/identity/claims/role
// for IsInRole() to work properly
if (claim.Type == "role")
{
context.Identity.AddClaim(new Claim(ClaimTypes.Role, claim.Value));
}
else if (claim.Type == "unique_name")
{
// map name to Identity.Name
context.Identity.AddClaim(new Claim(context.Identity.NameClaimType, claim.Value));
}
else
{
// this is optional, if you want any other specific claims from Active Directory
// this will also include some information about the jwt token such as the issue
// and expire times
context.Identity.AddClaim(new Claim(claim.Type, claim.Value));
}
}
return Task.CompletedTask;
}
};
Next, set the ClaimsIssuer to the ADFS url and set the SignInScheme to CookieAuthenticationDefaults.AuthenticationScheme and configure the AuthorizationEndpoint and TokenEndpoint to the proper endpoints on your ADFS server.
options.ClaimsIssuer = Configuration["ADFS:ServerUrl"];
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.AuthorizationEndpoint = Configuration["ADFS:ServerUrl"] + "/adfs/oauth2/authorize/";
options.TokenEndpoint = Configuration["ADFS:ServerUrl"] + "/adfs/oauth2/token/";
Finally, add the OAuth middleware using the options you've just created:
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOAuthAuthentication(options);
Now you should be able to apply the [Authorize] attribute to any controller or action which requires authorization with ADFS. For a complete sample application see this GitHub repo.
We have a web application written in ASP.NET that uses MVC for serving our Single Page Applications and Web API for ajax calls.
The authentication uses Microsoft.Owin and OpenIdConnect with Azure AD for Authority. The OAUTH flow is server side code authorization.
Then in Startup.Auth.cs we have
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
var cookieAuthenticationOptions = new CookieAuthenticationOptions()
{
CookieName = CookieName,
ExpireTimeSpan = TimeSpan.FromDays(30),
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
SlidingExpiration = true,
};
app.UseCookieAuthentication(cookieAuthenticationOptions);
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
AuthorizationCodeReceived = (context) =>
{
/*exchange authorization code for a token
stored on database to access API registered on AzureAD (using ADAL.NET) */
},
RedirectToIdentityProvider = (RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context) =>
{
/* Set the redirects URI here*/
},
});
}
When clicking on signin we navigate to an url whose routes map to the methods of the following MVC controller
public class AccountController : Controller
{
public void SignIn(string signalrRef)
{
var authenticationProperties = /* Proper auth properties, redirect etc.*/
HttpContext.GetOwinContext()
.Authentication.Challenge(authenticationProperties, OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
}
public void SignOut(string signalrRef)
{
var authenticationProperties = /* Proper auth properties, redirect etc.*/
HttpContext.GetOwinContext().Authentication.SignOut(authenticationProperties,
OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
}
Then the end-user connected to our application is authenticated between our client apps and the ASP.net server using an ASP.NET cookie. We would like to use Token Based approach instead. If you are interested this is the reason.
I tried to replace
the Nuget package Microsoft.Owin.Security.Cookies by Microsoft.Owin.Security.OAuth and in Startup.cs
replacing
app.UseCookieAuthentication(cookieAuthenticationOptions); by app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
and in my AccountController we changed the challenge from HttpContext.GetOwinContext().Authentication.SignOut(authenticationProperties,
OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType); to HttpContext.GetOwinContext().Authentication.SignOut(authenticationProperties,
OpenIdConnectAuthenticationDefaults.AuthenticationType, OAuthDefaults.AuthenticationType);
The problem is that with Cookie the set-cookie was automatically sent in web request respond when the flow completes while redirecting to the url we specified.
Where can I find the Bearer generated by OWIN with UseOAuthBearerAuthentication (if there is any) **, **Where and When should I send it back to my client SPAs
Note: an open source sample of what we are trying to do can be found in this github repository.
I think there are two approaches for you to consider.
Use javascript libraries to perform sign-in & token acquisition within your single page app. Then your backend is purely an web API, and can just use OAuth bearer middleware to authenticate requests. The backend doesn't know anything about signing the user in. We have a good sample that takes this approach here. If your backend needs to make API calls as well, you could consider the OnBehalfOf flow as well. I usually recommend this approach.
Use the OpenIDConnect middleware in your server to perform user sign-in and token acquisition. You might even be able to omit the usage of the CookieAuthenticationMiddleware entirely (although I'm not 100% sure). You can capture the token in the AuthorizationCodeReceived notification as you mention, and you could redirect back to your SPA with the token in the fragment of the URL. You could also have some route which delivers the tokens (which are cached on your server) down to your javascript. In either case, you'll need to ensure that an outside caller can't get access to your tokens.
The thing to keep in mind will be how you refresh tokens when they expire. If you use #1, most of it will be handled for you by libraries. If you use #2, you'll have to manage it more yourself.
Related to this problem: Owin Stage Markers
I'm using owin and identity framework to init an IIS hosted web app with authentication ...
public static void Configure(IAppBuilder app, IKernel kernel)
{
// ensure that owin creates the required UserManager & sign in manager per owin instance
app.CreatePerOwinContext<ApplicationUserManager>((options, owinContext) => ApplicationUserManager.Create(options, owinContext, kernel));
app.CreatePerOwinContext<ApplicationSignInManager>((options, owinContext) => ApplicationSignInManager.Create(options, owinContext, kernel));
GlobalFilters.Filters.Add(new AuthorizeAttribute());
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
app.Use((context, next) =>
{
// figure out if the user is in fact authenticated if not use "Guest" as the username here
var userName = context.Request?.User?.Identity?.Name ?? "Guest";
//THE QUESTION:
// Why at this point is context.Request.User a windows user with a username of ""
return next.Invoke();
}).UseStageMarker(PipelineStage.PostAuthenticate);
}
I'm not using windows auth anywhere, only bearer auth, and on the server windows auth is disabled within IIS, so how am I getting this "empty" identity and how can I fix this to get my token based identity from the authorization info in the current request?
Hmm,
It seems that Identityframework falls back to this state when it's authenticated but couldn't find a match.
I had a basic auth string in the header where it was looking for a bearer token which it couldn't validate.
I'm pretty sure this is odd behaviour though, some sort of auth failure / security exception might be a better solution here.
I have a bunch of web apps that I've created. Some of them use old-style webforms authentication, and some other sites at our company use different authentication patterns.
I'm trying to consolidate this, under one SSO pattern using Azure Active Directory. I've been trying to following guides/tutorials but it's not clicking for me.
My tech is currently ASP 4/MVC 5, although if ASP 5/MVC 6 is easier then I have the freedom to go that route as well. All web apps are hosted in Azure currently.
The confusion for me comes in that while looking through documentation, there seem to be so many ways to authentication and authorize users (also, authenticate vs authorize isn't absolutely clear to me).
I went to the Active Directory area of Azure Management portal (the old one). I added a new application called TestApp. I set the app URL to https://localhost:44320/, then sign-on URL to https://localhost:44320/, the tenant name to testapp. And my reply URL is https://localhost:44320/
This makes my app id uri https://localhost:44320/testapp I think? I also have my client ID guid.
The tutorial has an AccountController with a SignIn method like this:
public void SignIn()
{
// Send an OpenID Connect sign-in request.
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
When navigating to this, I receive the following in the browser:
[InvalidOperationException: IDX10803: Unable to create to obtain configuration from: 'https://localhost:44320/testapp/.well-known/openid-configuration'.]
I have a feeling it's because Azure is unable to redirect this all to back to my localhost? How can I set this up so I can test it out on Azure itself? And even further than that, will this solution be usable from multiple webapps? I'd assume they'd each be different applications in my Active Directory, but they'd all need to use a SSO procedure, where users can sign into multiple apps with one identity.
Sorry for any confusion, this is all very convoluted to me but I am trying to learn it as best I can.
Edit:
In the Startup of the webapp, I am calling this:
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
}
}
});
}
Which utilizes these app.config settings:
<add key="ida:ClientId" value="[my client id here]" />
<add key="ida:Tenant" value="testapp" />
<add key="ida:AADInstance" value="https://localhost:44320/{0}" />
<add key="ida:PostLogoutRedirectUri" value="https://localhost:44320/" />
Azure is able to redirect to localhost, it will just pop up a security confirmation asking if its ok to navigate to localhost.
Your tenant in app.config doesn't look right, change these app settings:
<add key="ida:Tenant" value="[YOUR TENANT].onmicrosoft.com" />
<add key="ida:AADInstance" value="https://login.microsoftonline.com/{0}" />
To find out more about your tenant see this article: How to get an Azure Active Directory tenant
You can also try adding this code to your Notifications in Startup (just under AuthenticationFailed), try putting breakpoints on the handlers to see what happens:
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
},
SecurityTokenValidated = (context) =>
{
return Task.FromResult(0);
}
Put an [Authorize] attribute on one of your controllers and it should redirect to the AAD authentication when you browse to it.
AFAIK each app would need a separate application in Azure AD and you would need to implement this Authentication in each separate app. I have managed to have a seamless sign in experience when I link via url from one app to another.
This answer sums up authentication vs authorization nicely: Authentication versus Authorization