IIS / ASP.NET - How to Provide ClaimsPrincipal? [closed] - asp.net

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
I want to have a (.NET Core) web api site. BEFORE a request comes into my site, it has to pass through a secondary site ("gateway"). The gateway will figure out the claims and create an IClaimsPrinciple with custom claims. As far as I am concerned, the gateway can set those claims by magic. My api will trust them 100%.
The gateway will then make its own request to my api, somehow attaching the claims info. When the request gets to my api site, the claims are already set.
How does the gateway site "attach" the claims to the http request?
An analogous (I think) use case is if IIS is set to windows auth. When I examine the static User object in my (unprotected) controller method, I can see it is a WindowsPrincipal, and its claims are things like AD user groups. My code (as far as I know) didn't do anything to the request to add those claims; it seems like IIS altered the request to attach those claims before it got to my site.
Is what I am asking possible? If so, how do you set the principle on a request? Or am I completely misunderstanding how principles are set? In the Windows Auth example, is it something in my .NET Project that's setting the WindowsPrinciple?

Are you asking, "how can I add custom claims to a user from inside an executable after the user has been authenticated but before the user tries to use a protected resource"?
By "protected resource" I mean anything protected with an [Authorize] attribute.
If that is what you are asking, I do not have a .Net Core example, but I do have a .Net Framework example, it is probably pretty easy to translate into .Net Core.
Yes, it is done in the middleware, in the Configuration method of your Startup class. In the example below I'm using Auth0 to do authentication. Below is the entire method, but scroll down to "SecurityTokenValidated" to see an example of adding a claim to an identity. In an actual application you would probably pull some unique key from the identity and then look up claims in a database.
public class Startup
{
public void Configuration(IAppBuilder app)
{
// Configure Auth0 parameters
string auth0Domain = ConfigurationManager.AppSettings["auth0:Domain"];
string auth0ClientId = ConfigurationManager.AppSettings["auth0:ClientId"];
string auth0ClientSecret = ConfigurationManager.AppSettings["auth0:ClientSecret"];
string auth0RedirectUri = ConfigurationManager.AppSettings["auth0:RedirectUri"];
string auth0PostLogoutRedirectUri = ConfigurationManager.AppSettings["auth0:PostLogoutRedirectUri"];
// Enable Kentor Cookie Saver middleware
app.UseKentorOwinCookieSaver();
// Set Cookies as default authentication type
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
LoginPath = new PathString("/Account/Login")
});
// Configure Auth0 authentication
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "Auth0",
Authority = $"https://{auth0Domain}",
ClientId = auth0ClientId,
ClientSecret = auth0ClientSecret,
RedirectUri = auth0RedirectUri,
PostLogoutRedirectUri = auth0PostLogoutRedirectUri,
ResponseType = OpenIdConnectResponseType.CodeIdToken,
Scope = "openid profile",
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = notification =>
{
if (notification.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
{
var logoutUri = $"https://{auth0Domain}/v2/logout?client_id={auth0ClientId}";
var postLogoutUri = notification.ProtocolMessage.PostLogoutRedirectUri;
if (!string.IsNullOrEmpty(postLogoutUri))
{
if (postLogoutUri.StartsWith("/"))
{
// transform to absolute
var request = notification.Request;
postLogoutUri = request.Scheme + "://" + request.Host + request.PathBase + postLogoutUri;
}
logoutUri += $"&returnTo={ Uri.EscapeDataString(postLogoutUri)}";
}
notification.Response.Redirect(logoutUri);
notification.HandleResponse();
}
return Task.FromResult(0);
},
//this fires when a user is redirected to Auth0 for authentication.
SecurityTokenValidated = (context) =>
{
var identity = context.AuthenticationTicket.Identity;
var uniqueKey = identity.FindFirst("MyUniqueKey");
//lookup something in database using unique key
identity.AddClaim(new System.Security.Claims.Claim(ClaimTypes.Role, "SomeRole"));
return Task.FromResult(0);
}
}
});
}
}
}

Related

Use two Owin identities in the same asp.net web application

Is it possible to use OWIN with two different authentications in the same time, e.g. Microsoft and Google?
In my ASP.NET web application, users authenticate initially with Azure OpenIdConnect to use the application.
At some point, user needs to authenticate with Google to perform few queries with Google (without overriding the Microsoft identity that will continue to be used).
I noticed that whenever I use Context.GetOwinContext().Authentication.Challenge(properties, "Google"), the authentication succeeded and I can call Google's API, but the Microsoft related claims, tokens and the whole identity are lost and replaced with the Google one, and I cannot anymore call Microsoft API unless I ask users to login again.
Is there any way to hold both identities so I can use them based on the need?
Since there was no answers and I could figure out the solution, Thanks to OWIN team support on GitHub, below is the solution:
Objective: Authenticate with more than one provider, and maintain both claims, so application can call both providers API's at any time.
In my case, Users must authenticate first with Azure Active Directory (OpenIdConnect) to be allowed to enter my application, plus to call Microsoft Graph API. Users also need to authenticate with Google, to make calls to Google API.
For OpenIdConnect, I'm using the default creation by visual studio
without any changes, and this is not the topic here.
How to add the second provider?
Tell OWIN that you are using the Google authentication during startup.
When user is trying to call Google API, check if it has Google related claims. if yes, use the access token and simply call the Google API, if Not, it means this is the first call to Google, so ask the application to authenticate with Google.
Once Authenticated with Google, save the claims (without overwriting OpenIdConnect's claims), so it can be used next time when calling Google API.
Now, let's see the details:
Below the main authentication (OpenIDConnect), Tell OWIN that you are using Google (by the way, this applies to any other provider). The most important part, is to tell OWIN to use different cookies for Google to save Google's claims in separate cookies. If you miss this step, the Google claims will overwrite the OpenIdConnect Claims, and you will not be able to call Microsoft Graph anymore.
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = AuthenticationConfig.ClientId,
Authority = AuthenticationConfig.Authority,
PostLogoutRedirectUri = AuthenticationConfig.PostLogoutRedirectUri,
RedirectUri = AuthenticationConfig.RedirectUri,
Scope = $"{AuthenticationConfig.BasicSignInScopes} User.Read",
SaveTokens=true,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailedAsync,
AuthorizationCodeReceived = OnAuthorizationCodeReceivedAsync,
SecurityTokenValidated = OnSecurityTokenValidatedAsync,
},
CookieManager = new Utils.SameSiteCookieManager(
new SystemWebCookieManager())
}
);
// Define New Cookies for Google
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Google",
AuthenticationMode = AuthenticationMode.Passive,
CookieName = CookieAuthenticationDefaults.CookiePrefix + "External.DocuSign",
});
// Tell OWIN to use Google with the special Cookies type
app.UseGoogleAuthentication(new Microsoft.Owin.Security.Google.GoogleOAuth2AuthenticationOptions()
{
ClientId = "xxxxxxxxxxxxxxxxxxxxxxx",
ClientSecret = "xxxxxxxxxxxxxxxxxx",
SignInAsAuthenticationType = "Google",
Provider = new Microsoft.Owin.Security.Google.GoogleOAuth2AuthenticationProvider() { OnAuthenticated = OnGoogleAuthenticated }
});
// This makes any middleware defined above this line run before the Authorization rule is applied in web.config
app.UseStageMarker(PipelineStage.Authenticate);
}
Before calling Google API, Check if Google Claims already exist. If yes, extract the access token and call Google API. If not, it means this is the first time you're trying to call Google, so authenticate first, save the claims, and then call the API.
var result = await Request.GetOwinContext().Authentication.AuthenticateAsync("Google");
if (result == null) // No Claims found for Google
{
// Redirect to Google for authentication
var properties = new AuthenticationProperties() { RedirectUri = "/" };
Context.GetOwinContext().Authentication.Challenge(properties, "Google");
}
else
{
// Get the Access Token from the google Claims
var accessToken = result.Identity.Claims.FirstOrDefault(a => a.Type == "google_access_token").Value;
// Now CALL Google API
}
Save the Google Claims after authenticating with Google. This is again in StartupAuth.cs in continuation to app.UseGoogeAuthentication where we override the event of getting google response, and we save the token to claims.
private static Task OnGoogleAuthenticated(Microsoft.Owin.Security.Google.GoogleOAuth2AuthenticatedContext context)
{
// Save the access token to Google Claims, to be used in Google API calls
context.Identity.AddClaim(new Claim("google_access_token", context.AccessToken));
if (context.RefreshToken != null)
{
context.Identity.AddClaim(new Claim("google_refresh_token", context.RefreshToken));
}
var expiresInSec = (long)(context.ExpiresIn.Value.TotalSeconds);
context.Identity.AddClaim(new Claim("google_expires_in", expiresInSec.ToString()));
return Task.FromResult(0);
}

Azure Active Directory SSO with MSAL and openID Connect

I was tasked with writing an ASP.NET website that uses Azure Active Directory. I went with the route of OAuth and OpenID Connect. I am not able to use implicit flow and therefore must set the ResponseType to be code.
Using MSAL code samples I got most of it working but the problem is that all the samples are using a response type that returns tokens. I think I need to do it in 2 separate steps, first get the authorization code and then get the id token. I'm not exactly sure how to do this and would much appreciate some guidance here.
I have a startup class that look like this:
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions { });
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
Authority = authority,
ClientId = clientId,
RedirectUri = redirectUri,
Scope = "openid profile email offline_access user.readbasic.all", // a basic set of permissions for user sign in & profile access
ResponseType = OpenIdConnectResponseType.Code,
ClientSecret = clientSecret,
TokenValidationParameters = new TokenValidationParameters
{
// In a real application you would use ValidateIssuer = true for additional checks and security.
ValidateIssuer = false,
NameClaimType = "name",
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
}
});
}
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
// Handle any unexpected errors during sign in
context.OwinContext.Response.Redirect("/Error?message=" + context.Exception.Message);
context.HandleResponse(); // Suppress the exception
return Task.FromResult(0);
}
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
/*
The `MSALPerUserMemoryTokenCache` is created and hooked in the `UserTokenCache` used by `IConfidentialClientApplication`.
At this point, if you inspect `ClaimsPrinciple.Current` you will notice that the Identity is still unauthenticated and it has no claims,
but `MSALPerUserMemoryTokenCache` needs the claims to work properly. Because of this sync problem, we are using the constructor that
receives `ClaimsPrincipal` as argument and we are getting the claims from the object `AuthorizationCodeReceivedNotification context`.
This object contains the property `AuthenticationTicket.Identity`, which is a `ClaimsIdentity`, created from the token received from
Azure AD and has a full set of claims.
*/
IConfidentialClientApplication confidentialClient = GroupManager.Utils.MsalAppBuilder.BuildConfidentialClientApplication(null);
// Upon successful sign in, get & cache a token using MSAL
AuthenticationResult result = await confidentialClient.AcquireTokenByAuthorizationCode(new[] { "openid profile email offline_access user.readbasic.all" }, context.Code).ExecuteAsync();
}
How do I take the information from the result's tokens and create a claims identity for the AuthenticationTicket.Identity and access the user info?
Please note that this is an ASP.NET application. Not MVC and not Core.
If you use MSAL, you don't need to handle the code yourself. MSAL will return the token to you after you log in interactively, please see:Overview of Microsoft Authentication Library (MSAL).
Before that, you need to take a look at Add sign-in to Microsoft to an ASP.NET web app,the workflow is:
Code example please check: https://github.com/AzureAdQuickstarts/AppModelv2-WebApp-OpenIDConnect-DotNet
Update:
Try to enable ID token

How do I configure ASP.NET WebApi to validate bearer tokens against an OpenID Connect server?

I am writing a service which receives POSTs from another service, which includes an Authorization header containing a bearer token. This token is obtained independently from an OpenID Connect server (Keycloak in our dev environment, but not necessarily in production). Our service does not need to obtain or issue tokens; it merely needs to be able to validate them.
We are using .NET Framework 4.8 with self-hosted ASP.NET WebApi (OWIN 4, etc).
Configuration-wise, the information we have is:
the URL of the OpenID Connect service, eg. 'http://keycloak:8080/auth/realms/demo/'
the client ID, eg. 'js-client'.
The intent is that we obtain the issuer public key dynamically, from the OpenID server's metadata endpoint 'http://keycloak:8080/auth/realms/demo/.well-known/openid-configuration'. Currently I have something like:
WebApp.Start(startOptions, builder => {
var config = ...
// ... Set up routes etc ...
config.Filters.Add(new HostAuthenticationFilter("Bearer"));
builder.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = "js-client",
Authority = "http://keycloak:8080/auth/realms/demo/",
RequireHttpsMetadata = false,
SignInAsAuthenticationType = "Bearer",
});
builder.UseWebApi(config);
}));
The controller action looks like:
[HttpGet]
[HttpPost]
[Authorize]
public IHttpActionResult Receive([FromBody] string dto) => Ok();
Currently, it always returns 401 Unauthorized with a message 'Authorization has been denied for this
request' irrespective of the validity of the token.
Wireshark reveals that our service never tries to contact the Keycloak server for OIDC metadata, so I guess that the authorisation handler is not even finding the token.
I've looked at UseJwtBearerAuthentication and UseOAuthAuthorizationServer too, but those seem to want more information than just an OIDC endpoint (unsurprising, really) or they need custom provider implementations.
This does not seem to be such an unusual use case that I need to implement my own validator, so presumably I'm missing something? Google searches turn up hundreds of examples which seem to relate only to ASP.NET Core or don't cover non-interactive use cases.
I managed to make progress on this by inspecting the source of OpenIdConnectAuthenticationMiddleware.
The JwtBearer middleware handles validation of the issuer, but needs to know the public key. Since I need to avoid configuring this directly, I need to ask the OIDC server for it.
This can be accomplished using a ConfigurationManager, which should deal with caching, etc for us:
private JwtBearerAuthenticationOptions GetJwtBearerTokenAuthenticationOptions(string issuer, IConfigurationManager<OpenIdConnectConfiguration> configurationManager)
{
return new JwtBearerAuthenticationOptions
{
Realm = "demo",
TokenValidationParameters = new TokenValidationParameters
{
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
// ... etc ...
IssuerSigningKeyResolver = (token, securitytoken, kid, validationparameters) =>
configurationManager.GetConfigurationAsync(CancellationToken.None).GetAwaiter().GetResult().SigningKeys,
ValidIssuer = issuer.TrimEnd('/'),
}
};
}
(The resolver delegate can't be async unfortunately, so I can't await this properly.)
The ConfigurationManager can be constructed like this (based on the internals of OpenIdConnectAuthenticationMiddleware):
private IConfigurationManager<OpenIdConnectConfiguration> GetOIDCConfigurationManager(string issuer)
{
var httpClient = new HttpClient(new WebRequestHandler());
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Demo OpenIdConnect middleware");
httpClient.Timeout = TimeSpan.FromMinutes(1);
httpClient.MaxResponseContentBufferSize = 10485760L;
var httpRetriever = new HttpDocumentRetriever(httpClient) { RequireHttps = false };
return new ConfigurationManager<OpenIdConnectConfiguration>($"{issuer}.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever(), httpRetriever);
}
These can then be used as follows:
const string issuer = "http://keycloak:8080/auth/realms/demo/";
var configurationManager = GetOIDCConfigurationManager(issuer);
builder.UseJwtBearerAuthentication(GetJwtBearerTokenAuthenticationOptions(issuer, configurationManager));
It all seems to work, although I'd very much like to know if there's a simpler way...?
Obviously, anyone using this in production should RequireHttps = true instead.

Validating Node.Js JWT token in asp.net/Authorize

I am in the process of splitting up my asp.net service to multiple micro services. As a process, I have created my identity service using Node.Js and it uses JWT for tokens.
Now i want to use this token in C# so that all my [Authorise] attributes use this token and allow access.
I have looked at many implementations, but could not get this to work. Since JWT is a standard impementation, i do not understand a reason why this would not work.
This is my C# code
public void ConfigureAuth(IAppBuilder app)
{
var issuer = "myorg/identity2";
string audienceId = ConfigurationManager.AppSettings["as:AudienceId"];
byte[] audienceSecret = TextEncodings.Base64Url.Decode
("xfecrrt7CV");
// Api controllers with an [Authorize] attribute will be validated with JWT
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audienceId },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, audienceSecret)
}
});
However, I get this error everytime i try to access a protected method.
{"Message":"Authorization has been denied for this request."}
Is there anything i am missing here? How do i add the claim identity to this?
Finally, it was resolved. One of my friends debugged the Identity source code and recommended to increased the key length. After increasing the key length, I was able to validate the token

Server side claims caching with Owin Authentication

I have an application that used to use FormsAuthentication, and a while ago I switched it to use the IdentityModel from WindowsIdentityFramework so that I could benefit from claims based authentication, but it was rather ugly to use and implement. So now I'm looking at OwinAuthentication.
I'm looking at OwinAuthentication and the Asp.Net Identity framework. But the Asp.Net Identity framework's only implementation at the moment uses EntityModel and I'm using nHibernate. So for now I'm looking to try bypassing Asp.Net Identity and just use the Owin Authentication directly. I was finally able to get a working login using the tips from "How do I ignore the Identity Framework magic and just use the OWIN auth middleware to get the claims I seek?", but now my cookie holding the claims is rather large. When I used the IdentityModel I was able to use a server side caching mechanism that cached the claims on the server and the cookie just held a simple token for the cached information. Is there a similar feature in OwinAuthentication, or would I have to implement it myself?
I expect I'm going to be in one of these boats...
The cookie stays as 3KB, oh well it's a little large.
Enable a feature similar to IdentityModel's SessionCaching in Owin that I don't know about.
Write my own implementation to cache the information causing the cookie to bloat and see if I can hook it up when I configure Owin at application startup.
I'm doing this all wrong and there's an approach I've not thought of or I'm misusing something in Owin.
public class OwinConfiguration
{
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Application",
AuthenticationMode = AuthenticationMode.Active,
CookieHttpOnly = true,
CookieName = "Application",
ExpireTimeSpan = TimeSpan.FromMinutes(30),
LoginPath = "/Login",
LogoutPath = "/Logout",
ReturnUrlParameter="ReturnUrl",
SlidingExpiration = true,
Provider = new CookieAuthenticationProvider()
{
OnValidateIdentity = async context =>
{
//handle custom caching here??
}
}
//CookieName = CookieAuthenticationDefaults.CookiePrefix + ExternalAuthentication.ExternalCookieName,
//ExpireTimeSpan = TimeSpan.FromMinutes(5),
});
}
}
UPDATE
I was able to get the desired effect using the information Hongye provided and I came up with the below logic...
Provider = new CookieAuthenticationProvider()
{
OnValidateIdentity = async context =>
{
var userId = context.Identity.GetUserId(); //Just a simple extension method to get the ID using identity.FindFirst(x => x.Type == ClaimTypes.NameIdentifier) and account for possible NULLs
if (userId == null) return;
var cacheKey = "MyApplication_Claim_Roles_" + userId.ToString();
var cachedClaims = System.Web.HttpContext.Current.Cache[cacheKey] as IEnumerable<Claim>;
if (cachedClaims == null)
{
var securityService = DependencyResolver.Current.GetService<ISecurityService>(); //My own service to get the user's roles from the database
cachedClaims = securityService.GetRoles(context.Identity.Name).Select(role => new Claim(ClaimTypes.Role, role.RoleName));
System.Web.HttpContext.Current.Cache[cacheKey] = cachedClaims;
}
context.Identity.AddClaims(cachedClaims);
}
}
OWIN cookie authentication middleware doesn't support session caching like feature yet. #2 is not an options.
#3 is the right way to go. As Prabu suggested, you should do following in your code:
OnResponseSignIn:
Save context.Identity in cache with a unique key(GUID)
Create a new ClaimsIdentity embedded with the unique key
Replace context.Identity with the new identity
OnValidateIdentity:
Get the unique key claim from context.Identity
Get the cached identity by the unique key
Call context.ReplaceIdentity with the cached identity
I was going to suggest you to gzip the cookie, but I found that OWIN already did that in its TicketSerializer. Not an option for you.
Provider = new CookieAuthenticationProvider()
{
OnResponseSignIn = async context =>
{
// This is the last chance before the ClaimsIdentity get serialized into a cookie.
// You can modify the ClaimsIdentity here and create the mapping here.
// This event is invoked one time on sign in.
},
OnValidateIdentity = async context =>
{
// This method gets invoked for every request after the cookie is converted
// into a ClaimsIdentity. Here you can look up your claims from the mapping table.
}
}
You can implement IAuthenticationSessionStore to store cookies into database.
Here's example for storing cookie in redis.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
SessionStore = new RedisSessionStore(new TicketDataFormat(dataProtector)),
LoginPath = new PathString("/Auth/LogOn"),
LogoutPath = new PathString("/Auth/LogOut"),
});
Check out full example at here

Resources