I have created a ResourceApi in my IndetityServer4 something like this:
I have defined a ApiResource called API 1 and specify directly claims - name, sub for this api resource and I've extended this resource and specify two scopes named Api1.Read and Api1.Write and specify for every scope a specific claims which I need for the specific part of API but I don't understand what is different between Claims used in ApiResource and Scopes?
What does mean Claims directly connected in ApiResource and Claims which is used in Scope?
I have tried restrict UserClaims in ApiResource only for sub and name but if I want in Api1.Write claim role it's sent in access token but in definition of Api1 is specify only name and sub - why is UserClaims defined in ApiResource?
var apiResource = new ApiResource
{
Name = "Api1",
UserClaims = new List<string> { "name", "sub" },
Scopes = new List<Scope>
{
new Scope
{
Name = "Api1.Read",
UserClaims = new List<string> {"sub", "name"}
},
new Scope
{
Name = "Api1.Write",
UserClaims = new List<string> {"sub", "name", "role"}
}
}
};
As per the documentation on ApiResource, the UserClaims in the ApiResource itself will always be included in the access token. If you divide that api in multiple Scope's, the UserClaims listed there will be added onto the UserClaims specified in the ApiResource.
Related
I am trying to run the sample at:
https://github.com/Azure-Samples/ms-identity-dotnetcore-b2c-account-management
And am receiving this error:
Enter command, then press ENTER: 7
Create a user with the custom attributes 'FavouriteSeason' (string) and 'LovesPets' (boolean)
Have you created the custom attributes 'FavouriteSeason' (string) and 'LovesPets' (boolean) in your tenant?
Code: Request_BadRequest
Message: One or more property values specified are invalid.
Inner error:
AdditionalData:
date: 2020-06-30T23:24:26
request-id: dad23cee-984b-439c-a943-9e1bc6be4c9b
ClientRequestId: dad23cee-984b-439c-a943-9e1bc6be4c9b
I have created the custom attributes and can clear see them in the tenant...it even returns their ids. I've setup the Graph access level appropriately (I believe).
Any ideas? Thank you!
In Code, you need to update Tenant id, client id, client secret and also give B2cExtensionAppClientId= “your application display name” in appsettings.json
You have to create two custom attributes in the B2C portal
I. FavouriteSeason (string)
II. LovesPets (boolean)
Use below code to create the user with the custom attribute
try
{
//Create user
var result = await graphClient.Users
.Request()
.AddAsync(new User
{
GivenName = "Casey",
Surname = "Jensen",
DisplayName = "Casey Jensen",
Identities = new List<ObjectIdentity>
{
new ObjectIdentity()
{
SignInType = "emailAddress",
Issuer ="AADCxPb2c.onmicrosoft.com",
IssuerAssignedId = "x#AADCxPb2c.onmicrosoft.com",
}
},
PasswordProfile = new PasswordProfile()
{
Password = b2c_ms_graph.Helpers.PasswordHelper.GenerateNewPassword(4, 8, 4)
},
PasswordPolicies = "DisablePasswordExpiration",
AdditionalData = extensionInstance
}) ;
Console.WriteLine(result);
string userId = result.Id;
Console.WriteLine(result.Id);
Console.WriteLine($"Created the new user. Now get the created user with object ID '{userId}'...");
Can find it on your registered App on Azure AD B2C:
Click on "API Permissions" on left panel.
Then Chose "APIs my organization uses".
You can find the value under name "b2c-extensions-app. Do not modify. Used by AADB2C for storing user data.".
Can we customize the scope in GoogleAuthProvider to get more details like their phone number, address or calendar, profile picture?
Also can we view the details of the Identity and access token and parse and save those results in our database?
You can register additional Scopes in the GoogleAuthProvider.Scopes collection which by default is populated with:
this.Scopes = new[] {
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/userinfo.email"
};
The OAuth Info from all ServiceStack's OAuth Providers are populated in the registered Auth Repository in the UserAuthDetails table where the Access Token is stored in AccessTokenSecret.
You can retrieve additional info about the user using the Access Token and overriding CreateAuthInfo in a custom GoogleAuthProvider and overriding the CreateAuthInfo() implementation which by default retrieves basic info about the user from the UserProfileUrl (https://www.googleapis.com/oauth2/v2/userinfo):
protected override Dictionary<string, string> CreateAuthInfo(string accessToken)
{
var url = this.UserProfileUrl.AddQueryParam("access_token", accessToken);
var json = url.GetJsonFromUrl();
var obj = JsonObject.Parse(json);
obj.MoveKey("id", "user_id");
obj.MoveKey("given_name", "first_name");
obj.MoveKey("family_name", "last_name");
obj.MoveKey("picture", AuthMetadataProvider.ProfileUrlKey, profileUrl => profileUrl.SanitizeOAuthUrl());
return obj;
}
The returned dictionary populates all well-known properties on UserAuthDetails in the overridable LoadUserAuthInfo() (which can alternatively be intercepted with the LoadUserAuthFilter on each AuthProvider). All other non-matching properties in the dictionary are saved in the Items Dictionary on the UserAuthDetails table.
Core 2.0, using AspnetCore.Identity. I created a few roles, including "Admin".
//initializing custom roles
var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
var UserManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
string[] roleNames = { "Admin", "Training", "Operations", "Membership", "Individual" };
IdentityResult roleResult;
foreach (var roleName in roleNames)
{
var roleExist = await RoleManager.RoleExistsAsync(roleName);
// ensure that the role does not exist
if (!roleExist)
{
//create the roles and seed them to the database:
roleResult = await RoleManager.CreateAsync(new IdentityRole(roleName));
}
}
I checked the SQL tables, and they're all there.:
Then I add myself to the Admin role (not the full method, but the relevant parts):
var UserManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
var _eric = await UserManager.FindByEmailAsync("username#gmail.com");
await UserManager.AddToRoleAsync(_eric, "Admin");
I check the tables, and I'm in there (along with another guy I added to a different role):
I then travel over to my method and slap Authorize on it with two of the roles, one of which I'm in (Admin):
[Authorize(Roles ="Training, Admin")]
public ActionResult Index()
{
return View();
}
And then I get access denied. I'm missing something, but can't figure out what step I messed up. User is in there, I'm logged in, the data tables show me as having the role assigned, and the Authorize tag looks good.
Roles are claims and claims are loaded only on sign in. If you modify a claim (such as by adding a role), you must sign the user out and either automatically sign them back in or prompt the user to re-authenticate, to reload the claims.
I've implemented custom authentication in my mobile services, but the claims that I add to my ClaimsIdentity object don't appear to be saved.
I create my ClaimsIdentity object, and then pass it to the CreateLoginResult method, as follows:
public IServiceTokenHandler Handler { get; set; }
...
ClaimsIdentity claimsIdentity = new ClaimsIdentity();
claimsIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, "username"));
claimsIdentity.AddClaim(new Claim(ClaimTypes.GivenName, "FirstName"));
claimsIdentity.AddClaim(new Claim(ClaimTypes.Surname, "LastName"));
LoginResult login = new CustomLoginProvider(Handler).CreateLoginResult(claimsIdentity, "masterkey");
If I call another method with the returned authorization token and try to retrieve the GivenName or Surname claims, they aren't available.
var identity = (ClaimsIdentity)User.Identity;
// 'claim' will be null
Claim claim = identity.FindFirst(ClaimTypes.GivenName);
Is this expected behaviour or am I doing something wrong? I'm making an assumption that the Claims in the ClaimsIdentity object being sent to CreateLoginResult are being saved against that authenticated user.
The ClaimsIdentity passed into this method does not get used fully unless you act on it in an overload of CreateCredentials(). First you should create a child class of ProviderCredentials with the fields you want. CreateCredentials() will be called by CreateLoginResult(), and it will get the same ClaimsIdentity as a parameter.
The returned ProviderCredentials gets stored, and you can always retrieve it again in your server code with a call to ServiceUser.GetIdentitiesAsync().
Simple Single Sign-On question
I Have two MVC4 applications:
**1**- http://localhost/BikeShop
ACS Relying Party:
- Name: **BikeShop**
- Return Url: **http://localhost/BikeShop**
- Token Format: **SAML 2.0**
**2**- http://localhost/BikePartsShop
ACS Relying Party:
- Name: **BikePartsShop**
- Return Url: **http://localhost/BikePartsShop**
- Token Format: **SAML 2.0**
The Scenario I have
I access BikeShop and the ACS Login Page is presented and I choose my Identity.
I now can do stuff on BikeShop.
Then I access BikePartsShop and the ACS Login Page is presented and I can choose my Identity.
The Scenario I must have
I access BikeShop and the ACS Login Page is presented and I choose my Identity.
I now can do stuff on BikeShop.
Then I access BikePartsShop and the ACS authorizes the same Identity
used in the BikeShop without further user intervention.
Has anyone implemented this scenario?
Best Regards, and thank you!
You can use the ACS management service to configure multiple reply addresses for the same relying party. See this link for details on how to add an RP. From the linked code sample, register more addresses as follows:
RelyingParty relyingParty = new RelyingParty()
{
Name = "BikeShop",
AsymmetricTokenEncryptionRequired = false,
TokenType = "SAML_2_0",
TokenLifetime = 3600
};
svc.AddToRelyingParties(relyingParty);
RelyingPartyAddress realm = new RelyingPartyAddress()
{
Address = "http://localhost/",
EndpointType = "Realm"
};
RelyingPartyAddress replyAddress1 = new RelyingPartyAddress()
{
Address = "http://localhost/BikeShop",
EndpointType = "Reply"
};
RelyingPartyAddress replyAddress2 = new RelyingPartyAddress()
{
Address = "http://localhost/BikePartsShop",
EndpointType = "Reply"
};
svc.AddRelatedObject(relyingParty, "RelyingPartyAddresses", realmAddress);
svc.AddRelatedObject(relyingParty, "RelyingPartyAddresses", replyAddress1);
svc.AddRelatedObject(relyingParty, "RelyingPartyAddresses", replyAddress2);
svc.SaveChanges(SaveChangesOptions.Batch);
Try out this code to help you forward to a specific identity provider, if you can figure out how to remember which identity provider they last used. The last login should be stored so that you will automatically 302 back to your app.
public IdentityProvider GetIdentityProvider(string identityProviderName, string realm , string audienceUri )
{
// acs config parameters
string acsNamespace = ConfigurationManager.AppSettings["ida:Namespace"];
realm = realm ?? Uri.EscapeDataString(ConfigurationManager.AppSettings["ida:Realm"]);
audienceUri = audienceUri ?? ConfigurationManager.AppSettings["ida:AudienceUri"];
string returnPath = Uri.EscapeDataString("/home/index");
var newReplyTo =
Uri.EscapeDataString(audienceUri.Replace(new Uri(audienceUri).Authority,
HttpContext.Current.Request.Url.Authority));
// retrieve current identity providers
string idpDiscoveryUrl = string.Format("{0}v2/metadata/IdentityProviders.js?protocol=wsfederation&realm={1}&reply_to={2}&context=rm%3d0%26id%3dpassive%26ru%3d{3}&request_id=&version=1.0", acsNamespace, realm, newReplyTo, returnPath);
string response = null;
using (var client = new WebClient()) {
response = client.DownloadString(idpDiscoveryUrl);
}
List<IdentityProvider> identityProviders = JsonConvert.DeserializeObject<List<IdentityProvider>>(response);
// lookup provider for tenant
var identityProvider = identityProviders.Where(i => i.Name == identityProviderName).FirstOrDefault() ?? new IdentityProvider();
return identityProvider;
}