OpenIdDict logout automatic so fast - c#

I use openiddict in netcore 2 project. the problem is after some request user be logout and i cant fix that by increase login time.
Here is my code to add openiddict
services.AddOpenIddict()
.AddCore(options =>
{
options.UseEntityFrameworkCore()
.UseDbContext<DataDbContext>()
.ReplaceDefaultEntities<Guid>();
})
.AddServer(options =>
{
options.UseMvc();
options.EnableTokenEndpoint("/api/token");
options.AllowPasswordFlow();
options.AcceptAnonymousClients();
options.DisableHttpsRequirement();
options.SetAccessTokenLifetime(TimeSpan.FromDays(10));
options.SetIdentityTokenLifetime(TimeSpan.FromDays(10));
})
.AddValidation();
services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name;
options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject;
options.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role;
options.Password.RequireDigit = false;
options.Password.RequiredLength = 4;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
options.Password.RequiredUniqueChars = 0;
options.Password.RequireNonAlphanumeric = false;
});
services.AddAuthentication(options =>
{
options.DefaultScheme = OpenIddictValidationDefaults.AuthenticationScheme;
});

after create token
var ticket =await CreateTicketAsync(request, user);
ticket.SetAccessTokenLifetime(TimeSpan.FromDays(7)); var tocken =
SignIn(ticket.Principal, ticket.Properties,
ticket.AuthenticationScheme);
i used it
private async Task<AuthenticationTicket> CreateTicketAsync(OpenIdConnectRequest request, ApplicationUser user)
{
// Create a new ClaimsPrincipal containing the claims that
// will be used to create an id_token, a token or a code.
var principal = await _signInManager.CreateUserPrincipalAsync(user);
// Create a new authentication ticket holding the user identity.
var ticket = new AuthenticationTicket(principal,
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
// ticket.Properties.ExpiresUtc = DateTime.Now.AddDays(2);
// Set the list of scopes granted to the client application.
ticket.SetScopes(new[]
{
OpenIdConnectConstants.Scopes.OpenId,
//OpenIdConnectConstants.Scopes.Email,
OpenIdConnectConstants.Scopes.Profile,
OpenIddictConstants.Scopes.Roles
}.Intersect(request.GetScopes()));
ticket.SetResources("resource-server");
// 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 ticket.Principal.Claims)
{
// Never include the security stamp in the access and identity tokens, as it's a secret value.
if (claim.Type == _identityOptions.Value.ClaimsIdentity.SecurityStampClaimType)
{
continue;
}
var destinations = new List<string>
{
OpenIdConnectConstants.Destinations.AccessToken
};
// Only add the iterated claim to the id_token if the corresponding scope was granted to the client application.
// The other claims will only be added to the access_token, which is encrypted when using the default format.
if ((claim.Type == OpenIdConnectConstants.Claims.Name && ticket.HasScope(OpenIdConnectConstants.Scopes.Profile)) ||
(claim.Type == OpenIdConnectConstants.Claims.Email && ticket.HasScope(OpenIdConnectConstants.Scopes.Email)) ||
(claim.Type == OpenIdConnectConstants.Claims.Role && ticket.HasScope(OpenIddictConstants.Claims.Roles)))
{
destinations.Add(OpenIdConnectConstants.Destinations.IdentityToken);
}
claim.SetDestinations(destinations);
}
return ticket;
}

Related

ASP.NET Core HttpxContext User is null but user is Authenticated

Basically, I can log in just fine in my Client and access all pages. If I log out, I can't access anything on my client, it always redirects to my login page. Directly accessing the api endpoint is another matter. Say I call mylocalapp.com/api/users, directly from the browser url bar, I can still get all data. So I added [Authorize]. Unexpectedly, I get a 401 if I do that, DESPITE being logged in. So I checked HttpContext.User and simply User.Identity and they're empty, despite successful authentication. ClaimsIdentity is also empty. Can you identify anything that I might have done wrong? According to what I found on Google, this IS how it should be done. Thanks for your help.
Login method:
Takes user data, checks if ok, creates a JWT token and gets browser cookies (if null - session expired) then appends the token to the browser. And then logs the user in via Sign In Manager. If ok, returns ok. Context var is just for debugging purposes.
[AllowAnonymous]
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
async public Task<IActionResult> LoginPost(AuthUser authUser)
{
var user = _context.Users.FirstOrDefault(user => user.Email == authUser.Email);
if (authUser is null) return BadRequest(AppResources.NullUser);
if (user is null) return BadRequest(AppResources.UserBadCredentials);
else
{
var isPassword = _userManager.CheckPasswordAsync(user, authUser.Password);
if (!isPassword.Result) return BadRequest(AppResources.UserBadCredentials);
var token = _jwtHandlerAuth.Authentication(authUser);
if (token == null) return BadRequest(AppResources.UserAuthenticationImpossible);
string cookieValue = Request.Cookies["jwt"];
var returnUser = new ReturnUser
{
Email = user.Email,
Name = $"{user.FirstName} {user.LastName}",
UserName = user.UserName
};
if (cookieValue != token)
{
Response.Cookies.Append("jwt", token, new CookieOptions
{
HttpOnly = true,
IsEssential = true,
SameSite = SameSiteMode.None,
Secure = true
});
}
var signInResult = await _signInManager.PasswordSignInAsync(authUser.Email, authUser.Password, false, false);
if (!signInResult.Succeeded) return BadRequest(AppResources.UserAuthenticationImpossible);
var context = _signInManager.Context;
return Ok(returnUser);
}
}
My Authentication method (creates the token):
public string Authentication(AuthUser authUser)
{
var tokenHandler = new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim("UserName", authUser.Email),
new Claim("Email", authUser.Email)
}),
Expires = DateTime.UtcNow.AddHours(2),
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(Encoding.UTF32.GetBytes(_privateKey)),
SecurityAlgorithms.HmacSha256Signature)
};
var claimsPrincipal = new ClaimsPrincipal(tokenDescriptor.Subject);
var token = tokenHandler.CreateToken(tokenDescriptor);
tokenHandler.WriteToken(token);
return tokenHandler.WriteToken(token);
}
And my Startup class services and middleware:
var builder = services.AddIdentityCore<User>();
var identityBuilder = new IdentityBuilder(builder.UserType, builder.Services);
identityBuilder.AddRoles<IdentityRole>();
identityBuilder.AddEntityFrameworkStores<IdentityContext>();
identityBuilder.AddSignInManager<SignInManager<User>>();
identityBuilder.AddDefaultTokenProviders();
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
x.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddCookie("Identity.Application").AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF32.GetBytes(Configuration.GetSection("Jwt:PrivateKey").Value)),
ValidateIssuer = false,
ValidateAudience = false
};
});
services.AddSingleton<IJwtHandlerAuth>(new JwtHandlerAuth(Configuration.GetSection("Jwt:PrivateKey").Value));
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

How to change the way to get token on OAuth Authentication c# .net core 3.1

In my company they recently have changed the way to get the OAuth token, they implemented a step that decrypts a new client's secret which they send to us on the server side. In this step, I need to execute a method called GetValidAuthorizationTokenByAuthorizationCode, where I send "Client ID", "Authorization Code" and redirect Uri:
dynamic token = tokenGenerator.GetValidAuthorizationTokenByAuthorizationCode(client_id, authorizationCode, redirectUri);
But the way I created it, the "authorization_code" was "hide", and I can't find a way to use my custom method.
Here is my ConfigureServices implementation:
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection();
services.AddSingleton(Configuration);
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.Configure<FormOptions>(options =>
{
options.ValueCountLimit = int.MaxValue;
options.ValueLengthLimit = int.MaxValue;
});
services.AddAuthentication(a =>
{
a.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
a.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
a.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOAuth("Auth0", options =>
{
// Configure the Auth0 Client ID and Client Secret
options.ClientId = Configuration["Auth0:ClientId"];
options.ClientSecret = Configuration["Auth0:ClientSecret"];
// Set the callback path
// Also ensure that you have added the URL as an Allowed Callback URL in your Auth0 dashboard
options.CallbackPath = new PathString("/Home/Login");
// Configure the Auth0 endpoints
options.AuthorizationEndpoint = $"https://{Configuration["Auth0:Domain"]}/authorize";
options.TokenEndpoint = $"https://{Configuration["Auth0:Domain"]}/token";
options.UserInformationEndpoint = $"https://{Configuration["Auth0:Domain"]}/tokeninfo";
// To save the tokens to the Authentication Properties we need to set this to true
// See code in OnTicketReceived event below to extract the tokens and save them as Claims
options.SaveTokens = true;
// Set scope to openid. See https://auth0.com/docs/scopes
options.Scope.Clear();
options.Scope.Add("openid");
options.Events = new OAuthEvents
{
// When creating a ticket we need to manually make the call to the User Info endpoint to retrieve the user's information,
// and subsequently extract the user's ID and email adddress and store them as claims
OnCreatingTicket = async context =>
{
// Retrieve user info
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await context.Backchannel.SendAsync(request, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
// Extract the user info object
var jsonResposta = JObject.Parse(await response.Content.ReadAsStringAsync());
var funcional = jsonResposta.SelectToken("jwt_token.usr").ToString();
//nome
var nome = jsonResposta.Value<string>("user_id");
if (!string.IsNullOrEmpty(nome))
{
context.Identity.AddClaim(new (ClaimsIdentity.DefaultNameClaimType, nome, ClaimValueTypes.String, context.Options.ClaimsIssuer));
}
// Funcional
if (!string.IsNullOrEmpty(funcional))
{
context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, funcional, ClaimValueTypes.String, context.Options.ClaimsIssuer));
}
}
};
});
;
services.Configure<FormOptions>(options =>
{
options.ValueCountLimit = int.MaxValue;
options.ValueLengthLimit = int.MaxValue;
});
services.AddControllersWithViews().AddRazorRuntimeCompilation();
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
// Timeout de 12 horas
options.IdleTimeout = TimeSpan.FromSeconds(43200);
options.Cookie.HttpOnly = true;
// Make the session cookie essential
options.Cookie.IsEssential = true;
});
services.AddSingleton(Configuration);
services.AddMvc();
services.AddControllers()
.AddJsonOptions(opts => opts.JsonSerializerOptions.PropertyNamingPolicy = null);
} ```
Is there any way to get the authorization code to customize the way of the middleware get the token?
I'm using Web MVC application with .net core 3.1.
Thanks :D
i finally solve this by doing manually the stepts that was done by middleware, and storage the token on cookie.

How to sign in an external ClaimsIdentity using IdentityUser Asp.Net Core 2.2?

Context Summary
I'm building a prototype WebApp which I want to integrate to Quickbooks Online.
This WebApp will in turn offer options to call a WebApi that will be responsible to make the requests against the Quickbooks Online WebApi.
In order to achieve this, I registered the Identity services to make it easier for me to have a login process etc.
Now, it seems that I'm confused between Individual Account (IdentityUser) and ClaimsIdentity.
Signing In
When I click the Login option from the top menu, I get correctly redirected to the Quickbooks Online authentication screen where:
I input my user credentials
I choose the company I wish to use
I authorize the WebApp to access the required scopes
On callback, I do a request against the user information endpoint and retrieve all user information needed.
sub
givenName
familyName
email
emailVerified
And
ClaimsIdentity.IsAuthenticated == true
So, I build the list of claims for the ClaimsPrincipal.Identity which just was authenticated externally, and try to sign it in.
var result = JsonConvert.DeserializeObject<Dictionary<string, string>>(response);
var user = (ClaimsIdentity)context.Principal.Identities.Single(claimsIdentity => claimsIdentity.AuthenticationType == "ecm-qbo");
user.AddClaim(new Claim(type: ClaimTypes.Name, result["givenName"]));
result.ToList().ForEach(kvp => {
user.AddClaim(new Claim(type: kvp.Key, value: kvp.Value, valueType: null, issuer: "qbo", originalIssuer: "qbo", subject: user));
});
But I get this error once my sign process is completed.
What can I do to avoid it, or to correct it so it signs my user in?
Code Sample
public void ConfigureServices(IServiceCollection services) {
services.Configure<CookiePolicyOptions>(options => {
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.ConfigureApplicationCookie(o => {
o.Cookie.Name = "auth_cookie";
o.Cookie.SameSite = SameSiteMode.None;
o.Events.OnRedirectToLogin = context => {
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
return Task.CompletedTask;
};
});
services.AddAuthentication(o => {
o.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddOAuth("qbo", "qbo", o => {
o.CallbackPath = new PathString("/signin-qbo");
o.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub");
o.ClaimActions.MapJsonKey(ClaimTypes.Email, "email");
o.ClaimActions.MapJsonKey(ClaimTypes.GivenName, "givenName");
o.ClaimActions.MapJsonKey(ClaimTypes.Surname, "familyName");
o.ClaimActions.MapJsonKey(ClaimTypes.Name, "givenName");
o.ClientId = Configuration["ecm.qbo.client-id"];
o.ClientSecret = Configuration["ecm.qbo.client-secret"];
o.SaveTokens = true;
o.ClaimsIssuer = "ecm-qbo";
o.Scope.Add("openid");
o.Scope.Add("profile");
o.Scope.Add("email");
o.Scope.Add("com.intuit.quickbooks.accounting");
o.AuthorizationEndpoint = Configuration["ecm.qbo.authorization-endpoint"];
o.TokenEndpoint = Configuration["ecm.qbo.token-endpoint"];
o.UserInformationEndpoint = Configuration["ecm.qbo.user-info-endpoint"];
o.Events.OnCreatingTicket = async context => {
var companyId = context.Request.Query["realmid"].FirstOrDefault() ?? throw new ArgumentNullException("realmId");
var accessToken = context.AccessToken;
var refreshToken = context.RefreshToken;
var tokens = context.Properties.GetTokens();
Configuration["ecm.qbo.access-token"] = accessToken;
Configuration["ecm.qbo.refresh-token"] = refreshToken;
Configuration["ecm.qbo.realm-id"] = companyId;
context.Backchannel.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
context.Backchannel.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await context.Backchannel.GetStringAsync(context.Options.UserInformationEndpoint);
var result = JsonConvert.DeserializeObject<Dictionary<string, string>>(response);
var user = (ClaimsIdentity)context.Principal.Identities.Single(claimsIdentity => claimsIdentity.AuthenticationType == "ecm-qbo");
user.AddClaim(new Claim(type: ClaimTypes.Name, result["givenName"]));
result.ToList().ForEach(kvp => {
user.AddClaim(new Claim(type: kvp.Key, value: kvp.Value, valueType: null, issuer: "qbo", originalIssuer: "qbo", subject: user));
});
await context.HttpContext.SignInAsync(IdentityConstants.ExternalScheme, context.Principal, new AuthenticationProperties {
IsPersistent = false,
RedirectUri = "/"
});
};
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<IdentityUser, IdentityRole>()
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

Provider claims in IProfileService

When I auth using oidc I get back a bunch of claims. If I do not add my custom IProfileService all of these claims are passed through in the id_token that identity server issues. If I provide my own ProfileService, the list of claims on the Subject is a subset of what comes back from the idp. Is there any way to get the full list in the profile service?
Here is the relevant info from Startup.cs:
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
}).AddProfileService<ProfileService>();
services.AddAuthentication()
.AddOpenIdConnect("Name", "Name", o =>
{
o.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
o.SignOutScheme = IdentityServerConstants.SignoutScheme;
o.Authority = "https://sub.domain.com/adfs/";
o.ClientId = "00000000-0000-0000-0000-000000000000";
o.ClientSecret = "secret";
o.ResponseType = "id_token";
o.SaveTokens = true;
o.CallbackPath = "/signin-adfs";
o.SignedOutCallbackPath = "/signout-callback-adfs";
o.RemoteSignOutPath = "/signout-adfs";
o.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
};
});
and my ProfileService:
public class ProfileService : IProfileService
{
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var objectGuidClaim = context.Subject.Claims.FirstOrDefault(x => x.Type == "ObjectGUID");
if (objectGuidClaim != null)
{
var userId = new Guid(Convert.FromBase64String(objectGuidClaim.Value));
context.IssuedClaims.Add(new Claim("UserId", userId.ToString()));
}
return Task.CompletedTask;
}
public Task IsActiveAsync(IsActiveContext context)
{
context.IsActive = true;
return Task.CompletedTask;
}
}
So in my case, without the ProfileService then ObjectGUID is passed through, but using the ProfileService, it's not available in context.Subject.Claims list.
My goal is to take the "ObjectGUID" claim from the idp which is a base64 encoded guid and convert it to a hex string and pass that along as the "UserId" claim from identity server.
I'm not even sure this is the best way. I've also tried converting it through ClaimActions but my action never executes (I tested with a random guid to make sure it wasn't something with the conversion):
o.ClaimActions.MapCustomJson("UserId", obj => {
return Guid.NewGuid().ToString();
});
Is this a better way? Why is it not executing?
Try to:
ensure your Subject does not contain
http://schemas.company.com/identity/claims/objectguid instead of just ObjectGUID
extend your
OpenIdConnect configuration with: o.GetClaimsFromUserInfoEndpoint =
true; together with o.ClaimActions.MapUniqueJsonKey("ObjectGUID", "ObjectGUID"); or o.ClaimActions.MapUniqueJsonKey("http://schemas.company.com/identity/claims/objectguid", "ObjectGUID");
if nothing before helped, try:
o.Events = new OpenIdConnectEvents
{
OnTicketReceived = context =>
{
var identity = context.Principal.Identity as ClaimsIdentity;
StringBuilder builder = new StringBuilder();
var claims = identity?.Claims.Select(x => $"{x.Type}:{x.Value};");
if (claims != null)
builder.AppendJoin(", ", claims);
Logger.LogInformation($"Ticket received: [Claims:{builder}]");
identity?.AddClaim(new Claim("userId", Guid.NewGuid().ToString()));
//you can embed your transformer here if you like
return Task.CompletedTask;
}};
(you can examine the exact incoming ticket here and leave the logging anyway for future purposes)

Update Umbraco user's user type, based on the roles from external identity

In the last few days I have been working on integrating Umbraco Backoffice with IdentityServer v3. I have managed to get to the point, where I authenticate user externally and have Umbraco create a user with some default user type in the backoffice and link it to the external account.
The next thing I'm doing is updating the Umbraco user type, based on the roles of the user. I think I found a way of doing that on linking the Umbraco to the external account, but I cannot see any way to constantly update the user types with each login, in case the roles were removed/added for a user.
By analyzing the code in Umbraco BackOfficeController, it seems there is no way to get into the process of authenticating and update data on the side of Umbraco.
var user = await UserManager.FindAsync(loginInfo.Login);
if (user != null)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
}
else
{
if (await AutoLinkAndSignInExternalAccount(loginInfo) == false)
{
ViewBag.ExternalSignInError = new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not been linked to to an account" };
}
}
It seems that if the umbraco login is found, then the user is just being logged in, without any exposed events or options. Only if the user is not found, then the whole process of creation and linking is started, where I could actually make some changes to the user properties.
That said, is there any way to actually update the user types of an Umbraco user, based on the claims from external server, on every login?
My code from the Startup class is below.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie
});
var idAuth = new OpenIdConnectAuthenticationOptions
{
Authority = "https://localhost:44332",
ClientId = "id",
ClientSecret = "secret",
RedirectUri = "http://localhost:8081/Umbraco",
ResponseType = "id_token token",
Scope = "openid profile roles email",
Caption = "test",
SignInAsAuthenticationType = Umbraco.Core.Constants.Security.BackOfficeExternalAuthenticationType
};
idAuth.Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async n =>
{
var id = n.AuthenticationTicket.Identity;
var givenName = id.FindFirst(System.Security.Claims.ClaimTypes.GivenName);
var familyName = id.FindFirst(System.Security.Claims.ClaimTypes.Surname);
var roles = id.FindAll(System.Security.Claims.ClaimTypes.Role);
var nid = new ClaimsIdentity(
id.AuthenticationType,
System.Security.Claims.ClaimTypes.GivenName,
System.Security.Claims.ClaimTypes.Role);
nid.AddClaim(givenName);
nid.AddClaim(familyName);
nid.AddClaims(roles);
nid.AddClaim(id.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier));
nid.AddClaim(id.FindFirst(System.Security.Claims.ClaimTypes.Email));
n.AuthenticationTicket = new AuthenticationTicket(
nid,
n.AuthenticationTicket.Properties);
}
};
//idAuth.AuthenticationType = "https://localhost:44332";
idAuth.ForUmbracoBackOffice("btn-google-plus", "fa-google-plus"); //temporary icon/button
idAuth.AuthenticationType = "https://localhost:44332";
var externalOptions = new ExternalSignInAutoLinkOptions(autoLinkExternalAccount: true, defaultUserType: "admin");
//externalOptions.OnAutoLinking; // TODO: set user type based on roles
idAuth.SetExternalSignInAutoLinkOptions(externalOptions);
app.UseOpenIdConnectAuthentication(idAuth);
Managed to solve this some time ago by manually checking the roles claim and Umbraco UserType on SecurityTokenValidated with the help of Umbraco services IExternalLoginService and IUserService. If the combination is not right (e.g. the administrator role is not present in the claim), I use Umbraco IUserService to update that user's UserType
Notifications =
new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async n =>
{
var id = n.AuthenticationTicket.Identity;
var uid = id.FindFirst(ClaimTypes.NameIdentifier);
var givenName = id.FindFirst(ClaimTypes.GivenName);
var familyName = id.FindFirst(ClaimTypes.Surname);
var roles = id.FindAll(ClaimTypes.Role);
var rolesList = roles as IList<Claim> ?? roles.ToList();
if (
!rolesList.Any(
c =>
string.Equals(c.Value, RoleNames.ContentEditor,
StringComparison.InvariantCultureIgnoreCase)))
throw new HttpException(403,
"You do not have any roles configured for the application");
// create new identity and set name and role claim type
var nid = new ClaimsIdentity(
id.AuthenticationType,
ClaimTypes.GivenName,
ClaimTypes.Role);
UpdateUserType(uid.Value, rolesList, applicationConfiguration.AuthorityUrl);
nid.AddClaim(givenName);
nid.AddClaim(familyName);
nid.AddClaims(rolesList);
nid.AddClaim(uid);
nid.AddClaim(id.FindFirst(ClaimTypes.Email));
n.AuthenticationTicket = new AuthenticationTicket(
nid,
n.AuthenticationTicket.Properties);
}
}
private static void UpdateUserType(string uid, IList<Claim> roles, string providerName)
{
var userService = ApplicationContext.Current.Services.UserService;
var oneUser = ApplicationContext.Current.Services.ExternalLoginService.Find(new UserLoginInfo(providerName, uid)).FirstOrDefault();
if (oneUser == null)
return;
var user = userService.GetUserById(oneUser.UserId);
if (user == null)
return;
if (
roles.Any(
r => string.Equals(r.Value, RoleNames.Administrator, StringComparison.InvariantCultureIgnoreCase))
&& !string.Equals(user.UserType.Alias, UmbracoRoleNames.Administrator))
{
SetUserType(user, UmbracoRoleNames.Administrator, userService);
return;
}
if (
roles.Any(
r => string.Equals(r.Value, RoleNames.ContentEditor, StringComparison.InvariantCultureIgnoreCase))
&& !string.Equals(user.UserType.Alias, UmbracoRoleNames.ContentEditor))
{
SetUserType(user, UmbracoRoleNames.ContentEditor, userService);
return;
}
}
private static void SetUserType(Umbraco.Core.Models.Membership.IUser user, string alias, IUserService userService)
{
try
{
user.UserType = userService.GetUserTypeByAlias(alias);
userService.Save(user);
}
catch (Exception e)
{
LogHelper.Error(typeof(ClassName), "Could not update the UserType of a user.", e);
}
}
In this specific case, I do not change the UserType back to a non-admin/non-content editor one when someone lacks that privilege from their roles claim, because they are being filtered out one step before and a 403 error code is being returned.

Categories