Keycloak: should UserName be used as logical key or NameIdentifier, or email address, or something else? - asp.net-core

In an ASP.net core application I'd like to store some information linked to each user.
Users are managed by Keycloak.
What is the best field to be chosen as "logical key" (that is: the foreign key used in my information table to link the user)? Should it be Keycloak's NameIdentifier (something like a GUID: "90666d8e-5efa-49d0-ba07-86cff816f1f3"), or the username (the one used by the user when logging in), or the email address or something else?

What is the best field to be chosen as "logical key" (that is: the
foreign key used in my information table to link the user)?
The best one will be the one that can 1) uniquely identify the user and 2) cannot be changed.
Username at first looks like a good candidate; but the username can be changed (Realm Settings > Login > Edit username set to ON), albeit by default it is readonly. The same applies to email address and many of the other user attributes.
The best key will be the user ID, represented in your case by the NameIdentifier. This Key uniquely identifies the user and its value cannot be changed. This Key will be injected into the tokens (e.g., access token) under the claim sub.

Related

Adding ASP.NET Identity to an existing project with already managed users table

I have an existing web api project with a users table. In general User is involved in some key business queries in the system (as other tables keep its 'UserId' foreign key).
These days I'm interested in adding Asp.net (core) identity. Basically I've already performed the required steps adding a separate Identity table, managing an additional db context (implementing IdentityDbContext), and also added a JWT token service. It looks that everything works fine. However I am now wondering how should I "link" between the authenticated user (which has logged in through the Identity module) and the user which is found on the other original "business related db".
What I was thinking of is that upon login, having the userId retrieved from the original Users table, based on the email which is used as the username and is found on both the original Users table and the new Identity table, and than have it kept as a Claim on the authenticated user. This way, each time the user is calling the API (for an Authorize marked action on the API relevant controller), assuming is authenticated I will have the relevant userId on hand and be able to address and query what ever is needed from the existing business table.
I guess this can work, however I'm not sure regarding this approach and I was wondering if there are any other options?
Regarding the option I've mentioned above, the main drawback I see is that upon the creation of a new user, this should be performed against 2 different tables, on 2 different DBs. In this case, in order to keep this in one unit of work, is it possible to create transaction scope consists of 2 different db contexts?
You're on the right track.
I faced similar problem
Imagine two different microservices.
Identity-Microservice(Stores identity information (Username, Password Etc...))
Employees-Microservice (Stores employee information (Name, Surname Etc...))
So how to establish a relationship between these two services?
Use queues(RabbitMq, Kafka etc...)
An event is created after User Registration(UserCreatedEvent {Id, Name etc..})
The workers microservice listens for this activity and records it in the corresponding table
This is the final state
Identity
Id = 1, UserName = ExampleUserName, Email = Example#Email Etc...
Employee
Id = 1, Name = ExampleName, Surname = ExampleSurname Etc...
Now both services are associated with each other.
Example
If i want to get the information of an employee who is logged in now.
var currentEmployeeId = User.Identity.GetById()
var employee = _db.Employee.GetById(currentEmployeeId)

Limit user in Fauna DB to read only his own data

We are using FaunaDB for storing users and users movie collection. For accessing data in Fauna Functions we use secret key which is binded to Roles.
Users document looks like this:
{
username:"John Doe",
email: "john#doe.com",
userID: "124"
}
Movie Collection document looks like this:
{
userID: "124",
movies:['Titanic','Forrest Gump']
}
To query user movie collection we call fauna function with role user and specific secret key generated for that role
Call(Function("Get_users_moive"), '124')
I would like to know is to possible to limit user, to query only his own data? In the function above if I give another userID it will retrun another user collection.
Thanks
You can certainly do this in Fauna. You can create "identity" documents in a Fauna database, and give those documents credentials that can be used to authenticate the user. Once a user authenticates, the secret for a token is created. From that point, the user can directly query Fauna using that secret, if you choose to allow them to do so.
In addition, Fauna has attributed-based access controls (ABAC). You define a role, which has two primary features: a privileges definition that lets you control which collections, indexes, documents, etc. that a user with the role has, and a membership definition which lets you specify which identities belong to the role.
A role's privileges and membership definitions can use predicates: functions that can make a dynamic determination of whether a privilege, or the membership should apply to the current query.
You can find out more in the documentation:
Tokens
ABAC
To ensure that a user can only access movie records that they created, you'll need to store a reference to the identity document within each movie document.
When a user uses the secret for the token that they acquired via the Login function, a role predicate can use the CurrentIdentity function to retrieve a reference to the associated identity document, and then compare that to the reference stored in the movie document. If those match, you grant read access, otherwise you don't.
For details, see:
Login
CurrentIdentity

Multiple Login Feature

I am implementing a multiple login system, using Facebook, Twitter, Email.
If the user login with Facebook, he is able to merge his account with Twitter account or Email account, so next time login, he can press on "Login with Twitter" or key in email+password to login.
Now the problem is the merging.
If user created account A using Facebook login, modify some data, then create account B using Twitter login, and modify some data, this would be hard to merge, because both accounts will have different data.
What do app/site who use multiple login handle this kind of situation? Or is it me who makes it complicated?
I guess the main point is not storing any detailed account information with the login method. This is probably what your user account DB looks like now:
- account_id: …
facebook_auth_info: …
username: …
birth_date: …
- account_id: …
twitter_auth_info: …
username: …
birth_date: …
Now this schema should make it a no-op to “merge accounts”:
- account_id: …
facebook_auth_info: …
twitter_auth_info: …
username: …
birth_date: …
In other words, treat Facebook, Twitter and other as a “identity providers” and allow for multiple identity providers per your own account.
The way I would do this is to keep separated third party login details and use tables like email and third party login details interchangeable to authenticate a user. This way you can have a customer that get authorized either by using email or using a third party login. This does not mean that the same user can login either with email&password or third party provider. This I find it helpful to avoid having all the time fields for third party providers in the User table and if you want to add more login providers you don't have to alter any table, just add another entry in Types table.
For example I would use a Users table where you you keep all details for a customer but login details. No email, password or any other login details. These details are held in separate tables and are linked by the PK from this table. This table will map 1:1 to the Emails table. For example a schema might be:
- Id (PK)
- FirstName
- SecondName
- etc.
Then I'll have an Emails table where I would keep the email and password for a user. You can also have extra details like Id and Verified (to know if email is verified). Please be aware of keeping plain text passwords or keeping pairs email - password in your db. This is an important security concern and I would not discuss it here. I would also use a UserId column to map emails to users. This will map 1:1 to Users table. A schema might be:
- Id (PK)
- Email
- Password
- UserId (FK)
- Verified
To handle the third party login details I'll use a table called ThirdPartyUsers. In this table I will store a UserId to map one record from this table to one record from User tables. Basically this table can replace Email table and you can move away from holding email - password in your db. In this table I would keep a ProviderCode that is a code from types table and a ProviderId that is an Id provided by third party logins. This Ids are unique per third party providers and don't change per user. You can pair a ProviderCode with a ProviderId to make a composite key. This will map 1:1 to Users table. A schema might be:
- Id (PK)
- UserId (FK)
- ProviderCode
- ProviderId
To have a unified User account I would have a MasterUsers table where you define a User as a Master user and you define into a link table its children. Through its children I understand any account that has the same email address. This means a classic account and any third party login providers. Using this you can a unique Master and multiple children. This would map 1:1 to Users table and 1:* to MasterLinkToChildren table. A schema might be :
- Id (PK)
- UserId
To link multiple customers to one MasterUser I would use a link table called MasterLinkToChildren (or any naming you prefer). In this I would have two columns for storing UserIds for childrens and MasterId for their parent. An important thing I would do is to make any MasterUser also be a children of himself in the Link table so when you update the MasterUser you would keep consistency between your records. This will map *:1 to MasterUsers and 1:1 to Users table. A schema might be:
- MasterId (FK)
- UserId (FK)
Finally I would have a Codes table where I would store unique code for each third party login provider. This would map 1:* to ThirdPartyUsers. A schema like:
- Id (PK)
- Code (Unique)
It might look like a lot of code and many tables but in this way you can preserve unique user login or you can unify everything under one Master account. I wouldn't merge them automatically but I will offer the user the option to do that. Another thing you need to consider is not to allow users to login or merge accounts before they verified their account. I didn't use a Verified column on ThirdPartyUsers table as I don't want to verify this type of users but you can do this as well.
By using a Codes table you can extend your third party login providers without altering other tables.

Can you use username as unique identifier/primary key

Just a quick question on the best practise for the below case.
Developing a website with accounts. Website is setup so that no two accounts can have the same username i.e. all usernames are unique.
When persisting accounts in a database, is it ok to use the username as a primary key (unique identifier) or is there some reasons I should be aware of that would require a separately generated unique id?
Don't use username as primary key, never.
Use surrogate keys (ie autogenerated numbers), because
they are faster and smaller (key is 4-8 bytes, username is up to you dont know bytes?)
it is only right now you suppose usernames will be unique, later you will find out that you need non-unique usernames (for example for deleted users for which you have to save transaction history), or requirments will change
users should be able to change their username, in case of error/typo/etc
UPDATE: in case of distributed systems, use GUIDs

What makes a keychain item unique (in iOS)?

My question concerns keychains in iOS (iPhone, iPad, ...). I think (but am not sure) that the implementation of keychains under Mac OS X raises the same question with the same answer.
iOS provides five types (classes) of keychain items. You must chose one of those five values for the key kSecClass to determine the type:
kSecClassGenericPassword used to store a generic password
kSecClassInternetPassword used to store an internet password
kSecClassCertificate used to store a certificate
kSecClassKey used to store a kryptographic key
kSecClassIdentity used to store an identity (certificate + private key)
After long time of reading apples documentation, blogs and forum-entries, I found out that a keychain item of type kSecClassGenericPassword gets its uniqueness from the attributes kSecAttrAccessGroup, kSecAttrAccount and kSecAttrService.
If those three attributes in request 1 are the same as in request 2, then you receive the same generic password keychain item, regardless of any other attributes. If one (or two or all) of this attributes changes its value, then you get different items.
But kSecAttrService is only available for items of type kSecClassGenericPassword, so it can't be part of the "unique key" of an item of any other type, and there seems to be no documentation that points out clearly which attributes uniquely determine a keychain item.
The sample code in the class "KeychainItemWrapper" of "GenericKeychain" uses the attribute kSecAttrGeneric to make an item unique, but this is a bug. The two entries in this example only are stored as two distinct entries, because their kSecAttrAccessGroup is different (one has the access group set, the other lets it free). If you try to add a 2nd password without an access group, using Apple's KeychainItemWrapper, you will fail.
So, please, answer my questions:
Is it true, that the combination of kSecAttrAccessGroup, kSecAttrAccount and kSecAttrService is the "unique key" of a keychain item whose kSecClass is kSecClassGenericPassword?
Which attributes makes a keychain item unique if its kSecClass is not kSecClassGenericPassword?
The primary keys are as follows (derived from open source files from Apple, see Schema.m4, KeySchema.m4 and SecItem.cpp):
For a keychain item of class kSecClassGenericPassword, the primary key is the combination of
kSecAttrAccount and kSecAttrService.
For a keychain item of class kSecClassInternetPassword, the primary key is the combination of kSecAttrAccount, kSecAttrSecurityDomain, kSecAttrServer, kSecAttrProtocol, kSecAttrAuthenticationType, kSecAttrPort and kSecAttrPath.
For a keychain item of class kSecClassCertificate, the primary key is the combination of kSecAttrCertificateType, kSecAttrIssuer and kSecAttrSerialNumber.
For a keychain item of class kSecClassKey, the primary key is the combination of kSecAttrApplicationLabel, kSecAttrApplicationTag, kSecAttrKeyType,
kSecAttrKeySizeInBits, kSecAttrEffectiveKeySize, and the creator, start date and end date which are not exposed by SecItem yet.
For a keychain item of class kSecClassIdentity I haven't found info on the primary key fields in the open source files, but as an identity is the combination of a private key and a certificate, I assume the primary key is the combination of the primary key fields for kSecClassKey and kSecClassCertificate.
As each keychain item belongs to a keychain access group, it feels like the keychain access group (field kSecAttrAccessGroup) is an added field to all these primary keys.
I was hitting a bug the other day (on iOS 7.1) that is related to this question. I was using SecItemCopyMatching to read a kSecClassGenericPassword item and it kept returning errSecItemNotFound (-25300) even though kSecAttrAccessGroup, kSecAttrAccount and kSecAttrService were all matching the item in the keychain.
Eventually I figured out that kSecAttrAccessible didn't match. The value in the keychain held pdmn = dk (kSecAttrAccessibleAlways), but I was using kSecAttrAccessibleWhenUnlocked.
Of course this value is not needed in the first place for SecItemCopyMatching, but the OSStatus was not errSecParam nor errSecBadReq but just errSecItemNotFound (-25300) which made it a bit tricky to find.
For SecItemUpdate I have experienced the same issue but in this method even using the same kSecAttrAccessible in the query parameter didn't work. Only completely removing this attribute fixed it.
I hope this comment will save few precious debugging moments for some of you.
Answer given by #Tammo Freese seems to be correct (but not mentioning all primary keys). I was searching for some proof in the documentation. Finally found:
Apple Documentation mentioning primary keys for each class of secret (quote below):
The system considers an item to be a duplicate for a given keychain when that keychain already has an item of the same class with the same set of composite primary keys. Each class of keychain item has a different set of primary keys, although a few attributes are used in common across all classes. In particular, where applicable, kSecAttrSynchronizable and kSecAttrAccessGroup are part of the set of primary keys. The additional per-class primary keys are listed below:
For generic passwords, the primary keys include kSecAttrAccount and
kSecAttrService.
For internet passwords, the primary keys include kSecAttrAccount,
kSecAttrSecurityDomain, kSecAttrServer, kSecAttrProtocol,
kSecAttrAuthenticationType, kSecAttrPort, and kSecAttrPath.
For certificates, the primary keys include kSecAttrCertificateType,
kSecAttrIssuer, and kSecAttrSerialNumber.
For key items, the primary keys include kSecAttrKeyClass,
kSecAttrKeyType, kSecAttrApplicationLabel, kSecAttrApplicationTag,
kSecAttrKeySizeInBits, and kSecAttrEffectiveKeySize.
For identity items, which are a certificate and a private key bundled
together, the primary keys are the same as for a certificate. Because
a private key may be certified more than once, the uniqueness of the
certificate determines that of the identity.
Here is another piece of useful information about uniqueness of a keychain item, found in the "Ensure Searchability" section of this Apple docs page.
To be able to find the item later, you’re going to use your knowledge of its attributes. In this example, the server and the account are the item’s distinguishing characteristics. For constant attributes (here, the server), use the same value during lookup. In contrast, the account attribute is dynamic, because it holds a value provided by the user at runtime. As long as your app never adds similar items with varying attributes (such as passwords for different accounts on the same server), you can omit these dynamic attributes as search parameters and instead retrieve them along with the item. As a result, when you look up the password, you also get the corresponding username.
If your app does add items with varying dynamic attributes, you’ll need a way to choose among them during retrieval. One option is to record information about the items in another way. For example, if you keep records of users in a Core Data model, you store the username there after using keychain services to store the password field. Later, you use the user name pulled from your data model to condition the search for the password.
In other cases, it may make sense to further characterize the item by adding more attributes. For example, you might include the kSecAttrLabel attribute in the original add query, providing a string that marks the item for the particular purpose. Then you’ll be able to use this attribute to narrow your search later.
Item of class kSecClassInternetPassword was used in the example, but there is a note that says:
Keychain services also offers the related kSecClassGenericPassword item class. Generic passwords are similar in most respects to Internet passwords, but they lack certain attributes specific to remote access (for example, they don’t have a kSecAttrServer attribute). When you don’t need these extra attributes, use a generic password instead.