I have a problem with windows auth and custom claims.
I have an identityServer with windows auth and User.Identity.Name show my AD name.
But I cannot understand, how should I add some properties from my storage to this user. I have now something like this:
var claims = new List<Claim> {
new Claim("devhomepage", "www.devsite.com", ClaimValueTypes.String)};
var userIdentity = new ClaimsIdentity(claims, "siteinfo");
User.AddIdentity(userIdentity);
await HttpContext.SignInAsync(User);
return RedirectToLocal(returnUrl);
and it doesn't work :-) my client will be not authorized.
here is a config for the server
new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = {"http://localhost:60640/signin-oidc"},// where to redirect to after login
PostLogoutRedirectUris = {"http://localhost:60640/signout-callback-oidc"},// where to redirect to after logout
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
},
AllowOfflineAccess = true,
RequireConsent = false
and is't a client
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "http://localhost:49245/";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.ClientSecret = "secret";
options.ResponseType = "code id_token";
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
});
For SignIn I must use Microsoft.AspNetCore.Identity.SignInManager class with method SignInAsync(/*I should take hire a TUser object, that I stored in my database, and make mapping with my AD account */)
To use custom claims (will be used for all samples with custom claims in IS4):
public class ProfileService : IProfileService
{
protected UserManager<IdentityUser> _userManager;
public ProfileService(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
//>Processing
var user = await _userManager.GetUserAsync(context.Subject);
//my custom claims
var claims = new List<Claim>
{
new Claim("devhomepage", "www"),
new Claim("reporting","reps")
};
context.IssuedClaims.AddRange(claims);
}
public async Task IsActiveAsync(IsActiveContext context)
{
//>Processing
var user = await _userManager.GetUserAsync(context.Subject);
context.IsActive = (user != null);
}
}
But this service I must register only after my IdentityService
services.AddIdentityServer()
...
services.AddTransient<IProfileService, ProfileService>();
}
of ConfigureServices
Related
I have correctly configured identity server 4 which authorizes a web api for method access. However, I cannot use the roles in the web api, the role is in the token but when it arrives on the web api it does not give me authorization to enter the api.
IDS4 Configuration
new Client
{
ClientId = "spaclient",
ClientName = "SPA Client",
RequireConsent = false,
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
RequirePkce = true,
RequireClientSecret = false,
AllowAccessTokensViaBrowser = true,
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"role"
}
}
public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[]
{
new ApiScope("spaclient", "SPA")
};
public static IEnumerable<ApiResource> ApiResources =>
new ApiResource[]
{
new ApiResource("spaclient", "SPA")
};
public static IEnumerable<IdentityResource> IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource("role","User Role", new List<string>() { "role" })
};
CLIENT CONFIG
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
builder.Services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://localhost:9002"; // --> IdentityServer Project
options.RequireHttpsMetadata = true;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
NameClaimType = "role",
RoleClaimType = "role"
};
});
CONTROLLER PART
[HttpGet]
[Authorize(Roles ="Administrator")] // <-- with role not work
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
[HttpGet]
[Authorize]<-- without role work fine
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
In your access token, there is no role claim. You need to configure your existing ApiScope or ApiResource to include the necessar role claim.
What you have done is to only include it in your ID-token.
see my answer here about the relationship between the various resource types in IdentityServer
To add a userclaim to your APIScope, like this:
new ApiScope(name: "spaclient",
displayName:"SPA",
userClaims: new List<string>{ "role" }),
Also, you must request the spaclient and openid scopes as well.
To control the token lifetimes:
var client2 = new Client
{
ClientId = "authcodeflowclient",
IdentityTokenLifetime = 300, //5 minutes
AccessTokenLifetime = 3600, //1 hour
AuthorizationCodeLifetime = 300, //5 minutes
AbsoluteRefreshTokenLifetime = 2592000, //30 days
SlidingRefreshTokenLifetime = 1296000, //15 days
...
To complement this answer, I write a blog post that goes into more detail about this topic:
IdentityServer – IdentityResource vs. ApiResource vs. ApiScope
I created project from template: "is4aspid" and added Azure Active Directory authentication.
In Startup.cs added this code:
// preserve OIDC state in cache (solves problems with AAD and URL lenghts)
services.AddOidcStateDataFormatterCache();
// cookie policy to deal with temporary browser incompatibilities
services.AddSameSiteCookiePolicy();
services.AddAuthentication()
.AddOpenIdConnect("aad", "Sign-in with Azure AD", options =>
{
options.Authority = "https://login.microsoftonline.com/common";
options.ClientId = "myClientId";
options.ClientSecret = "myClientSecret";
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.ResponseType = "id_token";
options.CallbackPath = "/signin-aad";
options.SignedOutCallbackPath = "/signout-callback-aad";
options.RemoteSignOutPath = "/signout-aad";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
NameClaimType = "name",
RoleClaimType = "role"
};
});
I added redirect uri on Azure: http://localhost:5000/signin-aad
Error occure where Callback method is called.
HttpContext.AuthenticateAsync(IdentityConstants.ExternalScheme) Success is always false
[HttpGet]
public async Task<IActionResult> Callback()
{
var result = await HttpContext.AuthenticateAsync(IdentityConstants.ExternalScheme);
if (result?.Succeeded != true)
{
throw new Exception("External authentication error");
}
// some code
}
Where is my error?
Solution
Must define IdentityConstants.ExternalScheme in startup and callback
I need to fill in the "Properties" in the client's claim.
I am writing down a claimon the IS4 server in the ProfileService class:
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
// ...
Claim claim = new Claim("userData", "personalRights");
string valuePersonalRights = JsonConvert.SerializeObject(userRights);
claim.Properties.Add(GetKeyValuePair("rights", valuePersonalRights));
claims.Add(claim);
context.IssuedClaims.AddRange(claims);
}
private KeyValuePair<string, string> GetKeyValuePair(string key, string value)
{
KeyValuePair<string, string> keyValuePair = new KeyValuePair<string, string>(key, value);
return keyValuePair;
}
In this claim on the server there are records "Properties":
https://postgres-russia.ru/wp-content/files/is4_img/on_server.jpg
However, on the client, the properties of this claim are missing:
https://postgres-russia.ru/wp-content/files/is4_img/on_client.jpg
Client Configuration:
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("domainGroups");
options.Scope.Add("geolocation");
options.Scope.Add("fullname");
options.SaveTokens = true;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
};
});
How to get claims properties on the client?
When you are defining your client, you can assign it's claims too, which will be included in access token.
public static IEnumerable<Client> Clients =>
new Client[]
{
new Client
{
ClientId = "spa",
ClientName = "SPA Client",
ClientUri = "",
AllowedGrantTypes = {GrantType.ResourceOwnerPassword,GrantType.ClientCredentials},
RedirectUris =
{
},
RequireClientSecret = false,
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
PostLogoutRedirectUris = { "" },
AllowedCorsOrigins = { "","" },
AllowedScopes = { "openid", "profile","roles", IdentityServerConstants.LocalApi.ScopeName },
Claims = new Claim[]//look at this property
{
new Claim("prop1","value1")
}
}
};
This is a common problem when using Net Core. For some reason, the Firefox browser was simply silent, without showing errors, and the Chrome browser pointed to 431 Request Header Fields Too Large. The size of the statements in the cookie was over 4096 byte. Solved by using the ITicketStore and storing the claims as claims in the user session. More details: stackoverrun.com/ru/q/11186809
More: IIS Deployed ASP.NET Core application giving intermittent 431 Request headers too long error
We have an existing ID server configuration and I am trying to work on the UI without having to set up the rest of the connections. I followed the example to set up an MVC client:
In Startup.cs:
Console.WriteLine("Adding Test Users!!!");
serviceBulder.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddTestUsers(Config.GetUsers());
New client added:
new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
RedirectUris = { "http://localhost:5002/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
}
}
Users are configured as the following:
public static List<TestUser> GetUsers()
{
return new List<TestUser>
{
new TestUser
{
SubjectId = "1",
Username = "alice",
Password = "password"
},
new TestUser
{
SubjectId = "2",
Username = "bob",
Password = "password"
},
};
}
MVC is configured to use the services:
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services
.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.ClientSecret = "secret";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("openid");
options.Scope.Add("profile");
});
}
I have a couple of issues:
When I use AllowedGrantTypes = GrantTypes.Implicit per tutorial I get "Sorry, there was an error : unauthorized_client"
When I use the AllowedGrantTypes = GrantTypes.Hybrid I do get the login screen but my in memory users (alice and bob with pw = password) can't log in.
Questions:
- Does AddTestUsers not work when there are additional data stores present already?
- Why does my grant type differ from the walkthrough?
I'd check on the AllowedGrantTypes at the identity server level. We have roughly the same setup and we use AllowedGrantTypes = GrantTypes.HybridAndClientCredentials This allows the MVC application to talk with identity server behind the scenes.
The test users should be present.
Does identity server 4 doesn't allow implicit flow to access API Resource.
Identity Server 4 config.cs
new Client
{
ClientId = "implicit",
ClientName = "Implicit Client",
AllowAccessTokensViaBrowser = true,
RedirectUris = { "https://notused" },
PostLogoutRedirectUris = { "https://notused" },
FrontChannelLogoutUri = "http://localhost:5000/signout-idsrv", // for testing identityserver on localhost
AccessTokenLifetime = 10,
AllowedGrantTypes = GrantTypes.Implicit,
AllowedScopes = { "openid", "profile", "email", "ProxyServer", "api" }
}
Api Resouce
new ApiResource("api", "Custom"),
new ApiResource("ProxyServer", "Proxy Server")
In Mvc Client I am using this code ConfigureServices
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
options.Cookie.Name = "mvcimplicit";
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = Constants.Authority;
options.RequireHttpsMetadata = false;
options.ClientId = "implicit";
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("ProxyServer");
options.Scope.Add("profile");
options.Scope.Add("email");
options.SaveTokens = true;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role,
};
});
When I try in browser I get "Sorry, there was an error : invalid_scope ". But if I remove options.Scope.Add("ProxyServer"); it works fine and Identity server 4 take me to login page.
OK I found the issue but posting just in case someone else face the same problem.
Response type needs to be specified explicitly otherwise it wont work.
options.ResponseType = "id_token token";
Modify the response type accordingly.