I have a React project with API (net core). My website menus/fields will be shown/hidden based on the Role of the user. The user will login to my website via external oidc.
However, the access_token and id_token coming from the oidc doesn't have the Role information, it will only have their email, which I will use to check against my Database to determine which Role is the logged in user. Currently I have an API to get Role based on their access_token, so it's something like
public string getRoles(string access_token)
{
//check Database
return role;
}
This function will be called in almost every page so I was wondering is there any more efficient way to do this?
You need to add the claim to the HttpContext.User when the signin is confirmed with the role from the DB. When you define this connection in your startup, be sure to handle the OnTokenValidated event.
.AddOpenIdConnect("oidc", options =>
{
options.Events = new OpenIdConnectEvents
{
OnTokenValidated = async ctx =>
{
var claim = new Claim("Role", "TheirRole");
var identity = new ClaimsIdentity(new[] { claim });
ctx.Principal.AddIdentity(identity);
await Task.CompletedTask;
}
};
}
Then you can access this within the controller (or anywhere with HttpContext) like so
var claim = HttpContext.User.Claims.First(c => c.Role == "TheirRole");
I'm building a web application which uses the cookie authentication built into ASP.NET Core 2.1.
I have my own sign in method which queries my own custom password verification and setting of claims. Roughly it looks like this:
public async Task<ActionResult<LoginResponse>> DoLogin([FromBody] LoginRequest req)
{
// fetch account and verify password
var claims = new List<Claim>
{
new Claim(ClaimTypes.Sid, account.AccountId.ToString(), ClaimValueTypes.Integer),
new Claim(ClaimTypes.Email, account.EmailAddress, ClaimValueTypes.Email),
new Claim(ClaimTypes.Role, "member", ClaimValueTypes.String)
};
var identity = new ClaimsIdentity(claims, "password");
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
return new LoginResponse
{
Success = true
};
}
I would like to conditionally render a "Log Out" button on various parts of the site if the user has the cookie which authenticates the user. Additionally I'd like to fetch the Sid claim so I can deliver personalized messaging on some public parts of the site.
The problem I have is that the way I have been fetching the Sid only works if my controller or controller action has an [Authorize] attribute on it. Without the [Authorize] attribute, the claim is missing.
Code:
public static int? GetNullableAccountId(this ClaimsPrincipal principal)
{
var claim = principal.FindFirst((Claim c) => { return c.Type == ClaimTypes.Sid; });
if (claim == null)
return null;
return int.Parse(claim.Value);
}
// then in the controller I try to get the account id:
var accountId = accessor.HttpContext.User.GetNullableAccountId();
// always null even when I have a valid cookie
I swear that I didn't need the [Authorize] attribute for this to work in prior versions of ASP.NET Core, but I couldn't find anything meaningful in change logs.
Is there some trick to getting ASP.NET Core to build the user identity on all calls or am I taking the wrong approach all together?
It seems it was a silly mistake. I was invoking app.UseAuthentication() after app.UseMvc() when configuring my application builder.
The documentation actually explicitly states the following:
Call the UseAuthentication method before calling UseMvcWithDefaultRoute or UseMvc
Source: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-2.2#configuration
I've asked a question before and the answer that was given was correct but the farther I go down this rabbit hole the more I realize; I don't think I was asking the right question.
Let me just explain this in the most simple terms I can... I have a AngularJS single page app (client), that points at an asp.net webapi (OWIN) site (Resource server?), and a separate asp.net "authorization/authentiation" server.
The auth server will provide authentication and authorization for multiple applications. I need to be able to use the Authorize attribute in the resource server, as well as get a token from from angular. I also need to use windows authentication (integrated) for everything, no usernames or passwords. The claims information is stored in a database and needs to be added to the token.
I've done a SSO style claims authoriztion implementation in asp.net core using openiddict with JwtBearerToken and 'password flow?' And wanted to try to do something similar (token, etc). I have a basic understanding of how that works from my previous implmentation, but I am completely lost trying to figure out how to get JWT working with Windows Auth. The answer to my previous question provided some good suggestions but I am having a hard time seeing how that applies in this scenario.
Currently I have been trying to get IdentityServer3 to do this, using the WindowsAuthentication extensions, mainly pulled from the samples. But I am really struggling to tie this together with the client and actually get something working. The current client and server code is below, mind you I really don't know if this is even close to the correct solution.
Client:
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Passive,
AuthenticationType = "windows",
Authority = "http://localhost:21989",
ClientId = "mvc.owin.implicit",
ClientSecret = "api-secret",
RequiredScopes = new[] { "api" }
});
AuthServer:
app.Map("/windows", ConfigureWindowsTokenProvider);
app.Use(async (context, next) =>
{
if (context.Request.Uri.AbsolutePath.EndsWith("/token", StringComparison.OrdinalIgnoreCase))
{
if (context.Authentication.User == null ||
!context.Authentication.User.Identity.IsAuthenticated)
{
context.Response.StatusCode = 401;
return;
}
}
await next();
});
var factory = new IdentityServerServiceFactory()
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get());
var options = new IdentityServerOptions
{
SigningCertificate = Certificate.Load(),
Factory = factory,
AuthenticationOptions = new AuthenticationOptions
{
EnableLocalLogin = false,
IdentityProviders = ConfigureIdentityProviders
},
RequireSsl = false
};
app.UseIdentityServer(options);
private static void ConfigureWindowsTokenProvider(IAppBuilder app)
{
app.UseWindowsAuthenticationService(new WindowsAuthenticationOptions
{
IdpReplyUrl = "http://localhost:21989",
SigningCertificate = Certificate.Load(),
EnableOAuth2Endpoint = false
});
}
private void ConfigureIdentityProviders(IAppBuilder app, string signInAsType)
{
var wsFederation = new WsFederationAuthenticationOptions
{
AuthenticationType = "windows",
Caption = "Windows",
SignInAsAuthenticationType = signInAsType,
MetadataAddress = "http://localhost:21989",
Wtrealm = "urn:idsrv3"
};
app.UseWsFederationAuthentication(wsFederation);
}
EDIT: I see the auth endpoints requests for "/.well-known/openid-configuration" as well as "/.well-known/jwks" and I have the Authorize attribute on a controller action which is being called, but I dont see anything else happening on the auth side. I also added a ICustomClaimsProvider implmentation to the usewindowsauthservice WindowsAuthenticationOptions but that doesnt even get called.
I've done a SSO style claims authoriztion implementation in asp.net core using openiddict with JwtBearerToken and 'password flow?'
If you were to use OpenIddict with Windows authentication, it would be quite easy to implement using the OAuth2/OpenID Connect implicit flow (which is the most appropriate flow for a JS app), without needing any WS-Federation proxy:
Startup configuration:
public void ConfigureServices(IServiceCollection services)
{
// Register the OpenIddict services.
services.AddOpenIddict(options =>
{
// Register the Entity Framework stores.
options.AddEntityFrameworkCoreStores<ApplicationDbContext>();
// Register the ASP.NET Core MVC binder used by OpenIddict.
// Note: if you don't call this method, you won't be able to
// bind OpenIdConnectRequest or OpenIdConnectResponse parameters.
options.AddMvcBinders();
// Enable the authorization endpoint.
options.EnableAuthorizationEndpoint("/connect/authorize");
// Enable the implicit flow.
options.AllowImplicitFlow();
// During development, you can disable the HTTPS requirement.
options.DisableHttpsRequirement();
// Register a new ephemeral key, that is discarded when the application
// shuts down. Tokens signed using this key are automatically invalidated.
// This method should only be used during development.
options.AddEphemeralSigningKey();
});
// Note: when using WebListener instead of IIS/Kestrel, the following lines must be uncommented:
//
// services.Configure<WebListenerOptions>(options =>
// {
// options.ListenerSettings.Authentication.AllowAnonymous = true;
// options.ListenerSettings.Authentication.Schemes = AuthenticationSchemes.Negotiate;
// });
}
Authorization controller:
public class AuthorizationController : Controller
{
// Warning: extreme caution must be taken to ensure the authorization endpoint is not included in a CORS policy
// that would allow an attacker to force a victim to silently authenticate with his Windows credentials
// and retrieve an access token using a cross-domain AJAX call. Avoiding CORS is strongly recommended.
[HttpGet("~/connect/authorize")]
public async Task<IActionResult> Authorize(OpenIdConnectRequest request)
{
// Retrieve the Windows principal: if a null value is returned, apply an HTTP challenge
// to allow IIS/WebListener to initiate the unmanaged integrated authentication dance.
var principal = await HttpContext.Authentication.AuthenticateAsync(IISDefaults.Negotiate);
if (principal == null)
{
return Challenge(IISDefaults.Negotiate);
}
// Note: while the principal is always a WindowsPrincipal object when using Kestrel behind IIS,
// a WindowsPrincipal instance must be manually created from the WindowsIdentity with WebListener.
var ticket = CreateTicket(request, principal as WindowsPrincipal ?? new WindowsPrincipal((WindowsIdentity) principal.Identity));
// Immediately return an authorization response without displaying a consent screen.
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
private AuthenticationTicket CreateTicket(OpenIdConnectRequest request, WindowsPrincipal principal)
{
// Create a new ClaimsIdentity containing the claims that
// will be used to create an id_token, a token or a code.
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
// Note: the JWT/OIDC "sub" claim is required by OpenIddict
// but is not automatically added to the Windows principal, so
// the primary security identifier is used as a fallback value.
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, principal.GetClaim(ClaimTypes.PrimarySid));
// Note: by default, claims are NOT automatically included in the access and identity tokens.
// To allow OpenIddict to serialize them, you must attach them a destination, that specifies
// whether they should be included in access tokens, in identity tokens or in both.
foreach (var claim in principal.Claims)
{
// In this sample, every claim is serialized in both the access and the identity tokens.
// In a real world application, you'd probably want to exclude confidential claims
// or apply a claims policy based on the scopes requested by the client application.
claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
// Copy the claim from the Windows principal to the new identity.
identity.AddClaim(claim);
}
// Create a new authentication ticket holding the user identity.
return new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
}
}
A similar scenario can be implemented in legacy ASP.NET apps using the OWIN/Katana version of ASOS, the OpenID Connect server middleware behind OpenIddict:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseOpenIdConnectServer(options =>
{
// Register a new ephemeral key, that is discarded when the application
// shuts down. Tokens signed using this key are automatically invalidated.
// This method should only be used during development.
options.SigningCredentials.AddEphemeralKey();
// Enable the authorization endpoint.
options.AuthorizationEndpointPath = new PathString("/connect/authorize");
// During development, you can disable the HTTPS requirement.
options.AllowInsecureHttp = true;
// Implement the ValidateAuthorizationRequest event to validate the response_type,
// the client_id and the redirect_uri provided by the client application.
options.Provider.OnValidateAuthorizationRequest = context =>
{
if (!context.Request.IsImplicitFlow())
{
context.Reject(
error: OpenIdConnectConstants.Errors.UnsupportedResponseType,
description: "The provided response_type is invalid.");
return Task.FromResult(0);
}
if (!string.Equals(context.ClientId, "spa-application", StringComparison.Ordinal))
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "The provided client_id is invalid.");
return Task.FromResult(0);
}
if (!string.Equals(context.RedirectUri, "http://spa-app.com/redirect_uri", StringComparison.Ordinal))
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "The provided redirect_uri is invalid.");
return Task.FromResult(0);
}
context.Validate();
return Task.FromResult(0);
};
// Implement the HandleAuthorizationRequest event to return an implicit authorization response.
options.Provider.OnHandleAuthorizationRequest = context =>
{
// Retrieve the Windows principal: if a null value is returned, apply an HTTP challenge
// to allow IIS/SystemWeb to initiate the unmanaged integrated authentication dance.
var principal = context.OwinContext.Authentication.User as WindowsPrincipal;
if (principal == null)
{
context.OwinContext.Authentication.Challenge();
return Task.FromResult(0);
}
// Create a new ClaimsIdentity containing the claims that
// will be used to create an id_token, a token or a code.
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationType);
// Note: the JWT/OIDC "sub" claim is required by OpenIddict
// but is not automatically added to the Windows principal, so
// the primary security identifier is used as a fallback value.
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, principal.GetClaim(ClaimTypes.PrimarySid));
// Note: by default, claims are NOT automatically included in the access and identity tokens.
// To allow OpenIddict to serialize them, you must attach them a destination, that specifies
// whether they should be included in access tokens, in identity tokens or in both.
foreach (var claim in principal.Claims)
{
// In this sample, every claim is serialized in both the access and the identity tokens.
// In a real world application, you'd probably want to exclude confidential claims
// or apply a claims policy based on the scopes requested by the client application.
claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
// Copy the claim from the Windows principal to the new identity.
identity.AddClaim(claim);
}
context.Validate(identity);
return Task.FromResult(0);
};
});
}
}
The client-side code shouldn't be different from any other JS application using the implicit flow. You can take a look at this sample to see how you can implement it with the oidc-client JS library: https://github.com/openiddict/openiddict-samples/tree/master/samples/ImplicitFlow/AureliaApp
So ultimately the whole point here was to augment claims on the existing ClaimsPrincipal with claims from the database and hopefully be able to use JWT's in the javascript. I was unable to get that to work using IdentityServer3. I ended up rolling my own rudimentary solution by implementing IAuthenticationFilter and IAuthorizationFilter using an attribute on the actions to supply the claim name.
First the authorize attribute does nothing but take the name of the claim that the user should have to access the action.
public class AuthorizeClaimAttribute : Attribute
{
public string ClaimValue;
public AuthorizeClaimAttribute(string value)
{
ClaimValue = value;
}
}
Then the Authorize filter which does nothing but check to see if the user has the claim from the attribute.
public class AuthorizeClaimFilter : AuthorizeAttribute, IAuthorizationFilter
{
private readonly string _claimValue;
public AuthorizeClaimFilter(string claimValue)
{
_claimValue = claimValue;
}
public override async Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
var p = actionContext.RequestContext.Principal as ClaimsPrincipal;
if(!p.HasClaim("process", _claimValue))
HandleUnauthorizedRequest(actionContext);
await Task.FromResult(0);
}
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden);
}
}
The Authentication filter which calls the webapi endpoint (which is using windows authentication) to get the users list of custom "claims" from the database. The WebAPI is just a standard webapi instance, nothing special at all.
public class ClaimAuthenticationFilter : ActionFilterAttribute, IAuthenticationFilter
{
public ClaimAuthenticationFilter()
{
}
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
if (context.Principal != null && context.Principal.Identity.IsAuthenticated)
{
var windowsPrincipal = context.Principal as WindowsPrincipal;
var handler = new HttpClientHandler()
{
UseDefaultCredentials = true
};
HttpClient client = new HttpClient(handler);
client.BaseAddress = new Uri("http://localhost:21989");// to be stored in config
var response = await client.GetAsync("/Security");
var contents = await response.Content.ReadAsStringAsync();
var claimsmodel = JsonConvert.DeserializeObject<List<ClaimsModel>>(contents);
if (windowsPrincipal != null)
{
var name = windowsPrincipal.Identity.Name;
var identity = new ClaimsIdentity();
foreach (var claim in claimsmodel)
{
identity.AddClaim(new Claim("process", claim.ClaimName));
}
var claimsPrincipal = new ClaimsPrincipal(identity);
context.Principal = claimsPrincipal;
}
}
await Task.FromResult(0);
}
public async Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
var challenge = new AuthenticationHeaderValue("Negotiate");
context.Result = new ResultWithChallenge(challenge, context.Result);
await Task.FromResult(0);
}
}
The filters are bound to the attribute using my DI framework (ninject in this case).
this.BindHttpFilter<AuthorizeClaimFilter>(FilterScope.Action)
.WhenActionMethodHas<AuthorizeClaimAttribute>()
.WithConstructorArgumentFromActionAttribute<AuthorizeClaimAttribute>("claimValue", o => o.ClaimValue);
This works for my purposes, and the web api endpoint consumable both in the WebAPI instance and in the AngularJS app. However it is obviously NOT ideal. I really would have preferred to use 'real' authentication/authorization processes. I hesitate to say this is the answer to the question, but it is the only solution I could come up with the time that I had to make something work.
I'm trying to separate a ASP.Net MVC login client from an authentication server that will use token bearers and will contain all the authentication business logic. Those 2 things sit in 2 different webroles
I'm pretty much done using the current implementation of Identity 2.0.
The UserManager and UserStore sit in the AuthServer and the login client knows nothing about the userId. Only the UserName.
For now, to generate the claims of a user in the client project, I use this implementation:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync()
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var claims = new List<Claim>()
{
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", this.UserName),
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", this.UserName),
new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "ASP.NET Identity"),
};
var claimsIdentity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
//var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return claimsIdentity;
}
As you can see, i got rid of the manager here but i'm afraid I'm missing some security features like the CookieAuthenticationProvider in Startup.Auth.cs who makes use of the SecurityStamp of the users.
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync())
}
I tried to call the the GenerateUserIdentityAsync of the AuthServer which calls manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie) function but I need the userID for that... thing that the client knows nothing about.
Any ideas? The code of CreateIdentityAsync seems to not be open source.
Thanks and sorry for the long post.
I just called the AuthenticationServer Api being authenticated by the token bearer in the authorization header. AuthServerProxy has been passed the bearer token taken from a cookie.
public async Task<ClaimsIdentity> GenerateUserIdentityAsync()
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await AuthServerProxy.CreateIdentityAsync(DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
I call the Authentication Server with the authorization header (bearer token)
[Route("CreateIdentity")]
public async Task<IHttpActionResult> CreateIdentity([FromBody] string authenticationType)
{
ApplicationUser user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
ClaimsIdentity userIdentity = await user.GenerateUserIdentityAsync(UserManager, authenticationType);
return Ok(userIdentity);
}
Therefore, there is no more UserManager in the Login client web role.
This can be cleaned up more but at least, the SoC is respected and EF disappeared from the client.
After testing the build-in MVC 5 OAuth2/OpenID providers I was able to create a website which allowed me authenticate myself using my Twitter credentials.
The problem I now encounter is that I also want to store the tokens (oauth_token & oauth_verifier) Twitter posts back, in the url, after a user has been successfully authenticated. I need these tokens so I can allow users to post details directly from my website to their twitter account.
After setting up the TwitterAuthenticationOptions (see below) in the Startup.Auth.cs I did found that the tokens that I'm after can be found in the context (((context.Response.Context).Request).QueryString) but parsing this seems an ugly solution.
var tw = new TwitterAuthenticationOptions {
ConsumerKey = "SecretKey",
ConsumerSecret = "SecretSecret",
SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie,
Provider = new TwitterAuthenticationProvider() {
OnAuthenticated = (context) => {
context.Identity.AddClaim(new System.Security.Claims.Claim("urn:twitter:access_token", context.AccessToken, XmlSchemaString, "Twitter"));
return Task.FromResult(0);
}
}
};
app.UseTwitterAuthentication(tw);
How can this gracefully be implemented? For Facebook I found a solution which actually retrieves additional information, this feel similar...
get-more-information-from-social-providers-used-in-the-vs-2013-project-templates
There is a good extension method in Request object. Add following lines in HomeController or controller wherever needed.
Request.GetOwinContext().Authentication.User.Claims // Lists all claims
// Filters by type
Request.GetOwinContext().Authentication.User.FindAll("urn:twitter:access_token")
GetOwinContext will give you the Authentication object where you can find the user object and them the claims.
I found a useful post here How do I access Microsoft.Owin.Security.xyz OnAuthenticated context AddClaims values?
I modified as mentioned in the steps in the post.
AccountController.cs
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
//New method call made here to persist the claims from external cookie
await SetExternalProperties(identity);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
//New method added to persist identity info
private async Task SetExternalProperties(ClaimsIdentity identity)
{
// get external claims captured in Startup.ConfigureAuth
ClaimsIdentity ext = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
if (ext != null)
{
var ignoreClaim = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims";
// add external claims to identity
foreach (var c in ext.Claims)
{
if (!c.Type.StartsWith(ignoreClaim))
if (!identity.HasClaim(c.Type, c.Value))
identity.AddClaim(c);
}
}
}
try this and let me know.
You can use Query instead of QueryString and then use Get method to retrieve value from query string.
context.Response.Context.Request.Query.Get("oauth_verifier");
context.Response.Context.Request.Query.Get("oauth_token"); or context.AccessToken
Other thining to note is that you shouldn't need oauth_verifer to post data. Look here at required headers. I suggest you to use one of libraries listended here to interact with Twitter.