i am making a set of applications that share a common oidc provider (in my control), where the users will be created.
One of my applications is a stateless SPA "meeting" app where you can schedule meetings with other users, and you login purely by an OIDC token.
I am having a hard time thinking a strategy about the following
Should the "user" details be stored in the meeting app after a login? So let's say user A exists in the provider, then enters the meeting app. Should i save user A in the meeting app DB?
How to handle change of user details? Let's say user A changes name to User B in the provider. Until he logs in again, all the other users see him as User A still in the "contacts" list. What is the usual practice for solving this?
How to handle deletions in the provider. I need someway to signal that "deleted in provider -> deleted in app". Should i constantly poll the provider and get any missing users, create a push system, or is this just unneeded?
Thanks a lot in advance
That's actually a very good question and rarely explained well in online articles. Hopefully the below detailed notes help you with your solution. I have answered your questions at the end.
OAUTH USER DATA
Typically the core user data such as name, email etc belongs in the Authorization Server. It contains Personally Identifiable Information (PII) and changes are audited there. This is explored in further detail in the Privacy and GDPR article.
DOMAIN SPECIFIC USER DATA
This might include fields like a user's application preferences, and you may end up with data similar to this in your APIs:
Field
Description
id
A database surrogate key for the user
subject
The subject claim from an OAuth access token, which is typically a GUID or something similar
archived
A boolean flag set to true when a user is active in the app
field 1
A domain specific value
field 2
A domain specific value
To get OAuth user data within your applications your APIs can call the Authorization Server's SCIM 2.0 endpoint as described in this User Management article.
AUTHORIZATION AND ROLES
Interestingly, roles and application specific rights could be stored in either of the above data sources. You may want to start by putting roles in the OAuth data, but for cases where they are very domain specific and change often, I have found that storing them in my own API data works best.
DOMAIN SPECIFIC USER DATA AND ACCESS TOKENS
Sometimes you need to include domain specific user data (which might include roles) in access tokens. This Claims Article explains how claims can be looked up from external APIs during token issuance. This typically involves a REST call from the Authorization Server to one or more APIs, providing the subject value for which tokens will be issued.
CONSISTENT USER IDENTITY IN YOUR APPS
A user can potentially authenticate in multiple ways, such as default password / corporate login / social login. You may need to use some custom Account Linking logic to ensure that the subject field in the access token gets the same value in all cases. This prevents you ever creating duplicate users within your application.
USER INFO CHANGES
These are typically made by end users within an application screen, and your APIs then call SCIM endpoints to update the core OAuth data. A common case is when a user changes their name and / or email, eg if the user gets married. Note that the subject value remains the same after this edit.
USER ADMINISTRATION
In scenarios where corporate assets are used, an administrator typically provisions users, either individually or in bulk. This can be done via the SCIM endpoint. In some cases administrator actions may need to save data to both data sources - eg to create a user and set roles + application preferences.
USER INFO EVENTS
Sometimes your application needs to know about a user info event, such as new, deleted or changed users. This can be managed via Event Listeners, where an extension to the Authorization Server calls back your domain specific APIs when a user edit occurs. When a user is deleted in the OAuth user data you might then update the user's application state to archived.
DATA MIGRATIONS
Finally it is worth mentioning that the above also supports migrating to an OAuth architecture or between providers:
Get a combined view of the user data before migration
Insert all existing users into the new OAuth system via SCIM
Update the combined view of the user data with new subject values
Update your domain specific data with new subject values
SUMMARY
So to answer your questions:
Aim to avoid this because it adds complexity, though in some cases you may need to denormalise for performance reasons. The OAuth user data should remain the source of truth and the only place where edits occur to PII data.
Your meeting app would need to join on the OAuth user data and domain specific user data and present a list. This would probably involve caching a combined view of the user data.
See Administrator Events above. Your API should be informed of OAuth user data changes via an event, then your SPA would get current data on the next refresh.
When implemented like this you end up with simple code and a well defined architecture. Some providers may not provide all of these features though, in which case you may need an alternative approach to some areas.
Related
I have OneLogin setup in my application and is working fine. Am using MongoDB database for storing the sessions, accounts and users.
And now, I imported user data from my old WordPress website(which doesn't uses OneLogin, but the native WordPress login).
So basically I imported the user data from WordPress and populated the users collection using the email_id, name, etc. When I login with the OneLogin into my application, it throws the error saying OAuthAccountNotLinked. When researched I can see that you are not recommending the auto-linking of user accounts for safety reasons. But in my case, it's a OneLogin provider that my client's organization that has now started using. And new OneLogin user registrations are manually approved by the admin. So security wise it won't be a problem. We are only using OneLogin as auth provider!
How can I setup auto-linking in this scenario? Because I have 10,000s of Users in my MongoDB collection(imported from old WordPress website). And each User is being requested to manually register at OneLogin using the same email id they were using before in the old WordPress website and is manually approved within the OneLogin.
Thanks
Quote right from the original site
Automatic account linking on sign in is not secure between arbitrary providers - with the exception of allowing users to sign in via an email addresses as a fallback (as they must verify their email address as part of the flow).
When an email address is associated with an OAuth account it does not necessarily mean that it has been verified as belonging to account holder — how email address verification is handled is not part of the OAuth specification and varies between providers (e.g. some do not verify first, some do verify first, others return metadata indicating the verification status).
With automatic account linking on sign in, this can be exploited by bad actors to hijack accounts by creating an OAuth account associated with the email address of another user.
For this reason it is not secure to automatically link accounts between arbitrary providers on sign in, which is why this feature is generally not provided by authentication service and is not provided by NextAuth.js.
Automatic account linking is seen on some sites, sometimes insecurely. It can be technically possible to do automatic account linking securely if you trust all the providers involved to ensure they have securely verified the email address associated with the account, but requires placing trust (and transferring the risk) to those providers to handle the process securely.
Examples of scenarios where this is secure include with an OAuth provider you control (e.g. that only authorizes users internal to your organization) or with a provider you explicitly trust to have verified the users email address.
Automatic account linking is not a planned feature of NextAuth.js, however there is scope to improve the user experience of account linking and of handling this flow, in a secure way. Typically this involves providing a fallback option to sign in via email, which is already possible (and recommended), but the current implementation of this flow could be improved on.
Providing support for secure account linking and unlinking of additional providers - which can only be done if a user is already signed in already - was originally a feature in v1.x but has not been present since v2.0, is planned to return in a future release.
You probably need to write your own implementation to handle such a situation. And call that implementation on each provider call back. Like a check, the email already exists in DB, through an extra level of verification like OTP etc. Finally, when everything passes, let the user in and store some extra info in the DB for future reference.
Since I wasn't able to find a straight forward solution, I come up with a workaround. It's not a perfect solution, but does the job. Maybe someone else will post a better solution here.
So what I did is, I have all the old user data imported to the users collection in my MongoDB database. And NextAuth uses this users, accounts and session collections to store the User and related data. Btw am storing the session data in database.
In my case, we have sent unique OneLogin registration URLs(created from the OneLogin Admin Dashboard) to our existing users for them to create a new account in OneLogin. Then when the User registers an account at OneLogin, the admin approves/rejects the User. If they are approved, they will be eligible to login to our app using their OneLogin credentials.
What I noticed is that, when a User tries to login, NextAuth checks the email field in users collection to find a matching record. Since we imported the Users from WordPress database, we only have records in users collection and nothing on the accounts collection. In accounts collection, NextAuth is storing the access_token, user_id (mapping to users collection), provider details, etc.
So during login, the NextAuth does the internal checks and finds the existing email in the users collection, but it fails to identify the user as there's no info about the provider(OneLogin) details.
Record from accounts collection:
So what I did is, updated all records in users collection by appending a _TEMP to the email field's values. For example, if there's a record with the value abc#abc.com, it will become abc#abc.com_TEMP
Then what I did is, I wrote an API route(/api/check_old_account) in my NextJS application, where it does the following:
gets the currently logged in User's email id (from NextAuth session)
searches the users collection for the same email id with a "_TEMP" at the end
if there exists a _TEMP version of the currently logged in User, then return a response saying an old account exists
Then in the HOME page, I wrote the code to call the above mentioned api (/api/check_old_account) if the User is logged in. This call is made only once in the HOME page, when everything is loaded. Here the login using OneLogin works without errors because we renamed the existing email id in users collection by appending _TEMP. So when User logins, the users collection inserts a new record with their email and related data to the accounts collection also. Basically there would be two records in users collection for that User now. One is their original email id record that got inserted now when the logged in (eg: abc#abc.com) and their old imported account (eg: abc#abc.com_TEMP). The reason why I wrote the code to call the API in the HOME page only is because, after User logs in, they will be redirected to the HOME page. So thought it won't be necessary to write the code globally or somewhere else.
So, if the above API response says that there's an old account that exists, I display a warning popup to the User saying that there's an old account existing in the database and asks whether they want to link that old account. If the User presses YES button, I make an API call to /api/link_old_account. In this API route, the code is written to perform this:
if there's a _TEMP version email of the currently logged in user in users collection, find the respective record _id that has mappings in the accounts collection.
then change the value of the userId field of that respective record(currently logged in user id) in accounts collection, with the user id of the record with the _TEMP version email.
then delete the record with the _id of the currently logged in user
then update the email field by removing the _TEMP from it
then deletes the records that matches the currently logged in user in the userId field of sessions collection. So that the currently logged in sessions of this User would be invalidated.
then redirects the user back to the HOME page using res.redirect(307, '/')
The solution seems to be working fine so far.
When creating an Angular web application that also has a backend API, I feel like there are a few different options when it comes to getting User Info such as roles/permissions/display name/email/etc.
We can use an ID Token to store user claims like this. That token can be put into local storage or a cookie and the Angular app can read it and render the UI/guard against unauthorized route navigation/etc as soon as the app spins up (since the ID token is available right then and there).
We can NOT use an ID Token for this information at all and instead have an API endpoint that we have to call every page re-load to fetch this data. The server would decode our access token/ID token and return the data in JSON format.
Lastly, there could be some hybrid solution where basic User Info like names/emails are stored int he ID token and available right away, but user permissions (which could be a larger payload and maybe not wanted in a token that should be small) could be fetched via an API
Is there maybe a 4th option I didn't think about?
I haven't been able to find many conventions around which of these options is the best. I like the ID token option as it requires no "blocking" of the UI until the API request is done making the page load that much faster, but I'm not sure if that goes against other conventions.
All your approaches rely on a permissions-based system where you would have been granted permissions upon login. These are sometimes referred to as birth rights since they are typically given when the user is created or whenever their permission sets change. The typical way to carry birth rights around is to have them as scopes / assertions inside an identity token (e.g. OAUth 2.0) that you pass along from service to service.
You can also have your applications retrieve additional permissions / roles / entitlements from a backend store (a database for instance) based on the user ID so that you know what your user can or cannot do.
So far this is essentially role-based access control / permissions-based access control.
The main challenge with that approach is role explosion / permissions explosion as well as token bloat (too many permissions in the token) and administration pains - you have to assign roles and permissions to users all the time. You have to deprovision. It becomes a management nightmare and a risk you may have the wrong permissions set for users. You then need to think about identity and access governance as well as recertification. Heavy.
What's the alternative?
You definitely need some roles - yes - but they should be kept to a minimum - essentially the business roles you need in your apps e.g. a doctor, a nurse, a non-medical staff rather than doctor_hospital1_unitA.
You should then express your authorization as plain-old English policies using any number of attributes - not just user attributes but also contextual information (time, location), resource information (what type of object, who owns it, where is it? How sensitive is it?), and action information (view, edit, delete...).
Sample Policies
A doctor can view a medical record if they are assigned to the patient the medical record belongs to
A nurse can view a medical record if the medical record is in the same unit as the nurse
A non-medical staff can view the financial section of a medical record but not the medical section.
Attribute-Based Access Control
Following this approach is called attribute-based access control (abac). In ABAC, you clearly decouple your app from the authorization process. Authorization is expressed as policies rather than code which makes it easier to:
update
audit
review
How to implement?
You have several options to implement ABAC (from open-source to commercial). You can go down the XACML (xacml) path, the ALFA alfa path, or others. They all have similar architectures with:
the notion of a policy decision point (PDP): a service that evaluates the authorization requests against the set of policies you defined and produce decisions (Permit / Deny) that can be enriched with additional information e.g. order to do two-factor Authentication.
the notion of a policy enforcement point (PEP): an interceptor that sits in front of or inside your API that will send an authorization request to the PDP.
I've written about the architecture more in detail in this SO post.
ALFA Example
In ALFA, a sample policy would look like:
policyset viewMedicalRecord{
target clause object == "medical record" and action == "view"
apply firstApplicable
policy allowDoctors{
target clause role == "doctor"
apply firstApplicable
rule allowAssignedPatient{
permit
condition patient.assignedDoctor == user.name
}
}
}
I understand the usage of claims for things I would commonly refer to as "roles" or "permissions". I know that claims are more general, but from what I have seen in practice, it usually boils down to this: If user has this set of claims they can access certain areas, or perform certain functions.
Imagine a wiki application. You might have a content_contributor claim that would allow a user to add content, a content_admin claim that would allow a user to remove content, and a modify_user claim that would allow the granting of contributor rights to other user.
Taking this example a step farther, I may want to restrict users so that they can only see content created by themselves or their team.
If a user can only see content created by themselves, would we have a claim for each piece of content they created, or would we delegate that authorization to the application?
When you are talking about roles and permissions then you are talking about authorization.
Claims are typically not for authorization. (Identity)Claims are there to model the identity of the user: who is the user? The claims on itself do not tell anything about authorization. A user can have a role claim, but this doesn't tell the application what the user is allowed to do.
Authorization is done by the application, based on who the user is. Think of authorization as a set of rules, like:
18+: allow when user is older than 18 (DateOfBirth).
Use car: allow when user has a drivers license.
Or something like that.
Roles are a bit confusing, as they are often misused for authorization. Please read this article for some background information.
The problem with roles IMO is that these are not universal. I can be a Doctor in one hospital, while I'm a Patient in another. And I can be Admin for one tenant, but a User for another tenant. So they have only meaning within a certain context.
The only reason to include roles as claim is that you won't need to lookup this information as it is already present. But given the previous remark, you actually can't include this information. And it will only give you headaches when you do. Because you can't do simple things like update or change permissions or profile settings, until the user logs in again.
So as a rule of thumb: keep authorization close to the resource (api / website). Because that is the place where the business rules are implemented. And that's the place where you can store and update permissions, etc.
Keep a seperation of concerns when it comes to authentication and authorization. Authentication tells you who the user is, and authorization tells you what the user is allowed to do. Don't mix these two.
Translating this to your wiki application:
Create a seperate context where you store authorization information like roles and permissions. You can manage this in a central resource (for multiple applications) or use the context in your application. I would not mix this context with the business context.
Add a user in the authorization context and add a role content_contributor. Within the application read the permissions (from the central API, the local authorization context, a settings file, or anything that suits best) for that user (based on the sub claim). Cache it to speed up performance, and apply the rules to determine whether the user is allowed to access the resource.
You can extend this with resource-based authorization. Save the value of the sub claim in the content record to identify the owner. When the current user matches the sub claim value, then the current user is the owner.
You can use the same approach for teams. Add a teams table to the business context and link the user to one or more teams. Directly using the sub claim value or indirectly, using a Users table, also in the business context, where the user is linked to the sub claim value. You can add name, etc. in case you want to show this information (like in a report).
You can save team id and / or user id or sub claim value (owner is member of the same team as current user) in the content record in order to determine the allowed access for the user.
My setup would be like this:
Identity context: users + userclaims. For authentication only. Application independent.
Authorization context: users (id = sub claim) + per application: roles, permissions, etc. In seperate 'local' databases or in a central database. For authorization only.
Business context: users (Id, Name, 'foreign key' sub claim, without the actual database relation as the table is outside the context) + teams, profile, settings, etc. Linked to the sub claim value when users table is omitted.
In order to keep the users table in the business context up-to-date, periodically refresh the values. You can for instance update values when the user logs in after x time. Or once in a while query the Identity Context (using the API) to request user information (using the identities User Info endpoint).
In all contexts there can be a users table, but they all have a different meaning and contain other information. So there is no redundant information.
Authorization takes place inside the application and is based on the business rules (policies) and authorization information from the authorization context.
As a final remark, when the current system requires role claims (like for User.IsInRole() or [Authorize("role")]) then you can read (from cache) the role / permissions on each call and add these to the claims collection of the current user (claims transformation).
I am trying to build a pure JavaScript rest-client application that must support anonymous retrieval of information from a REST server that already supports JWT for authentication/authorization for external applications. The server is already being used by other client applications supporting multi-tenancy. Actually embedding the tenant information in the JWT.
Besides that the application needs to support users(human beings) that will want to mark(or select) some resources as favorites so a mechanism is needed for users/role creation and further authentication/authorization for the users. But these users can't be isolated to a single tenant, they will want to use across tenant resources.
So, right now I found that I need to use a JWT value for the anonymous data retrieval that of course should be tenant-agnostic. This means that I have to create an user with a special role that just have permissions for read only resources, except for the permissions for user creation (when the clients do sign up) again this should be tenant-agnostic. And when the user log-in into the system the JWT should be replaced for the one that have the user credentials again tenant agnostic. I am not sure if this is entirely correct, so how should we handle a situation like this ?
My other concern is, that we have the same back-end supporting authentication and credentials storage for human clients (tenant-agnostic) and application clients (tenant-aware), so there is logic that is a little bit more complicated in order to handle the privileges and tenant restrictions here. This could be just my impression but I feel that there should be a separation between application users and human users in the logic and/or data store.
But I am not completely sure and I want to know if some of you have previous experience or could have some ideas about this topic ?
Can you try the following approach, Create the users, assign the users with a read-only role for the tenants to which they need access to.
The data would be like
User1 - tenant1 - administrative role
User1 - tenant2 - data reader role
User1 - tenant 3 - user role
In the jwt, we ensure that the user is authorized. Then we get the list of accessible tenants and see if he has access to the requested tenant data w.r.to the above data and then complete the authorization.
HTH
We are currently looking into implementing our own STS (Microsoft WIF) for authenticating our users, and in that process we have come up with a few questions that we haven’t been able to answer.
We have different kinds of users, using different kinds of applications. Each kind of user needs some special types of claims only relevant for that kind of users and the application belonging.
Note that we do not control all the clients.
Let’s say that all users are authorized using simple https using username and password (.NET MVC3). A user is uniquely identified by its type, username and password (not username and password alone). So I will need to create an endpoint for each user type, to be able to differentiate between them. When a user authorize, I’ll issue a token containing a claim representing the user type. Is there an easier way for doing this? Can I avoid an endpoint for each user type (currently there are three)?
My token service can then examine the authorized users’ token and transform the claims, issuing a token containing all the users’ type specific claims. So far so good, except for the multiple endpoints I think?
If I need to have multiple endpoints, should I expose different metadata documents as well, one for each endpoint? Having one big metadata document containing a description of all claims, doesn’t make any sense since there is no application that needs all claims.
Update
Some clarifications.
Certain applications are only used by certain types of users. Not one application can be used by multiple user types.
Depending on what type of application the request is coming from, username and passwords needs to be compared for that user type. There are user stores for each type of application. That is why I need to know what application type the request is coming from. I can't resolve the type by the username and password alone.
Based on your problem description, it sounds like you have three indepent user "repositories" (one for each user type).
So imho this would be a valid scenario for three STS or a STS with multiple endpoints.
Another way to solve this could be to distinguish the user type by the indentifier of the replying party redirecting the user to the sts. This identifier is submitted in the wtrealm parameter.
The processing sequence could look like the following:
Get configuration for relying party (wtrealm) from configuration store (I'd suggest a database for your rather complex case)
Validate user with username, password and user type (from relying party configuration)
Add claims depending on user type or relying party specific configuration.
The datasbase/class structure for this could look similiar to this:
Need some more information to answer:
Are certain applications only used by certain types of users? Or can any user type access any application? If the former, you can configure the STS for that application to pass that user type as a claim. Each application can be configured to have its own subset of claims.
Where is the user type derived from? If from a repository, could you not simply construct a claim for it?
Update:
#Peter's solution should work.
Wrt. the 3 STS vs. 3 endpoints,
Many STS - can use same standard endpoint with different "code-behind". Would still work if you migrated to an out-the box solution . Extra work for certificate renewals.
One STS - custom endpoints won't be able to be migrated. Only one STS to update for certificate renewals.
Metadata - given that it can be generated dynamically, doesn't really matter. Refer Generating Federation Metadata Dynamically.