Relation to users when these are stored in an external Identity provider service - azure

I'm trying to create an API and a website client for it. Lately I've been reading a lot about OAuth2 as a security mechanism and companies that offers authentication as a service such as auth0.com or even Azure active Directory and I can see the advantages in using them
Because I'm used to always having the users in the same database and tables with relationships to the Users table in the form of One to Many such as below
public class User
{
public string subjectId { get; set; }
public virtual List<Invoice> Invoices { get; set; }
/*
More properties in here
*/
}
public class Invoice
{
public int InvoiceId { get; set; }
public string PaymentNumber { get; set; }
public DateTime Date { get; set; }
public double Amount { get; set; }
public string Description { get; set; }
public virtual User User { get; set; }
}
My questions is then.
If the users are stored in an external authentication service such as Auth0.com,
How the Invoice class will handle the relation to the user?
Would it be just adding a new property subjectId in the Invoice table and this will take the value of whatever id the authentication service assigned?
In the latter case, would the class Invoice be something like below?
public class Invoice
{
public int InvoiceId { get; set; }
public string PaymentNumber { get; set; }
public DateTime Date { get; set; }
public double Amount { get; set; }
public string Description { get; set; }
public string SubjectId{get;set;}
}
Also, if the users are stored someplace else, how do you make a query like,
Select * from Users u inner join Invoices i where Users.Name='John Doe' and i.Date>Somedate.

Since you have mentioned Auth0 as your Identity provider there are multiple ways to achieve the user table in your database.
1. Authenticating/ registering the user with Auth0 will send a response with Profile Object which will have all the basic profile information you need. Post this profile object back to your own API to save it to database. This API endpoint should be secured with the access token you received along with the profile object from Auth0.
2. You can create a custom rule in Auth0 that posts the user information back to your api. This rule gets executed on Auth0 server so this is a secure call.
3. Identity providers (Auth0 in our case) are required to expose an API endpoint that gives us user profile data (ex: https://yourdoamin.auth0.com/userinfo). You can make a call to this endpoint from your API to receive the user information.
When user Registers to your application, please use one of these techniques to establish a User profile information table in your database. It is always a good idea to treat the Identity Provider as a service responsible for authenticating the resource owner (the user of your application) and providing an access token for securely accessing your API/ application. If you have the profile of the user in your database, you do not have to depend on the Identity Provider once the user is authenticated.
Please let me know if you have any further questions.
Thank you,
Soma.

We have a similar setup for our website. We use Passport for our user database and our website doesn't have a user table at all. This makes life much simpler than having a a bunch of duplicate data between Passport and our website. I'll use our code as an example of what you are doing and hopefully it makes sense.
Our website has a License object that looks like this (Java not C#, but they are similar):
public class License {
public String companyName;
public List<User> users;
}
The License table looks like this (trimmed down):
CREATE TABLE licenses (
id UUID NOT NULL,
company_name VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
);
The License identifies the users that are associated with it via a join table like this (Passport uses UUIDs for user ids making life simple again):
CREATE TABLE users_licenses (
users_id UUID NOT NULL,
licenses_id UUID NOT NULL,
PRIMARY KEY (users_id, licenses_id),
CONSTRAINT users_licenses_fk_1 FOREIGN KEY (licenses_id) REFERENCES licenses (id)
);
Then we can select in either direction. If we know the user id, we can ask for all their licenses like this:
select * from licenses where users_id = ?
Or if we know the license id, we can ask for all the users that have access to the license:
select * from users_licenses where licenses_id = ?
Once we have one or more user ids, we can call the Passport /api/user endpoint or the /api/user/search endpoint to retrieve one or more user objects. We are actually using the Passport Java Client (https://github.com/inversoft/passport-java-client) which makes the API call for us and then returns a List<User>. This is what is stored in the License class from above. That code looks like this:
License license = licenseMapper.retrieveById(licenseId);
List<UUID> userIds = licenseMapper.retrieveUserIdsFor(licenseId);
ClientResponse<SearchResponse, Errors> clientResponse = passportClient.searchUsers(userIds);
license.users = clientResponse.successResponse.users;
LicenseMapper is a MyBatis interface that executes the SQL and returns the License objects. C# ORMs use LINQ, but it would be similar.
The nice thing about this setup is that we don't have a user database table in our website database that we have to keep in sync. Everything is loaded from Passport via the API. We aren't ever concerned about performance either. Passport is on-premise and can do thousands of user lookups each second, so we always load the data instead of caching it.
The only piece of your question that requires additional code is handling the joins when you are searching for arbitrary users like name='John Doe'. The only way to handle this is to query your user database first, retrieve all the IDs, then load their invoices. This seems like it could be dangerous if you have a large user database, but still doable.
That could would look like this in our situation:
UserSearchCriteria criteria = new UserSearchCriteria().withName("John Doe");
ClientResponse<SearchResponse, Errors> clientResponse = passportClient.searchUsersByQueryString(criteria);
List<User> users = clientResponse.successResponse.users;
Set<License> licenses = new HashSet<>();
for (User user : users) {
licenses.addAll(licenseMapper.retrieveByUserId(user.id));
}

Related

Structuring Data in Firebase Cloud Firestore

I had been trying to structure my database in firestore
my json looks like this
{"UID":"myusrsid","PhotoList":[{"PhotoID":333,"PhotoURL":"url1ofphone"}, {"PhotoID":332,"PhotoURL":"url2ofphoto"} ]}
Here UID is UserID and its unique to users and this UID inside this have Photos List which have photo id and its url
so whenever user upload a new photo if the userid already exist the photo should be added to Photos List
and if new user then UID with new user should be created and photo should be added in photo list
I had been scratching my head to structure this
public class Users
{
public string UID { get; set; }
public List<PhotoList> PhotoList { get; set; }
}
public class PhotoList
{
public int PhotoID { get; set; }
public string PhotoURL { get; set; }
}
this is my model which I am planing to send and receive data
I am aware of retrieving and storing the data the only thing is right now I need help with how should I structure it. I am using Node js
here is what I had been doing right now
const imageBody = {
uid,
imageBase64String,
} = req.body;
await db.collection('users').doc(imageBody.uid).collection('Photos').doc(imageBody.imageBase64String).create({
url : imageBody.imageBase64String
});
Firestore has a maximum document size, so storing binary files is not recommended. Rather use Firebase Storage and store the URL in Firestore.
If you still want to store the images in Firestore consider using a flatter structure. You can still maintain strong access control and it will also be easier for other users share photos. By putting access control in the document itself you can get really powerful sharing capability without sacrificing security. So something like:
Users
Photos
ownerUID
imageBase64String
readPrivs
And to access images for a user:
await db.collection("Photos").where("ownerUID", "==", uid).get();

Azure mobile app and Xamarin

I am trying to make a mobile app, which will use the Azure database system. I am having alot of trouble making my own table, and have been running in coding circles for a couple of weeks. I just can't figure out what and how to change.
I can get the todolist up and running from azure, and i have tried to make my own table in the backend with a dataobject and a controller, but after adding the DbSet om the context, the todolist part breaks when i try to run the app.
How do i add my own stuff to the app, so that i can have a table of persons for example, instead of the todolist?
Thank you so much in advance. this is very confusing to me.
This is what i've done:
In the backend, i made a person class inhereting the EntityData class and have a firstname string property and a lastname string property
Then i added
public DbSet<Person> Persons { get; set; }
and then a Personcontroller through the Add -> Controller -> Azure Mobile
Apps Table Controller in visual studio 2017
Then in the app i downloaded from azure, i made the person class
public class Person
{
[JsonProperty(PropertyName = "firstName")]
public string firstName { get; set; }
[JsonProperty(PropertyName = "lastName")]
public string lastName { get; set; }
[JsonProperty(PropertyName = "id")]
public string id { get; set; }
}
Then made the table
IMobileServiceTable<Person> PersonTable = client.GetTable<Person>();
Then tried to insert into the table
Person peter = new Person();
peter.firstName = "Peter";
peter.lastName = "Friis";
await personTable.InsertAsync(peter);
but that gives the error:
Microsoft.WindowsAzure.MobileServices.MobileServiceInvalidOperationException:
'The request could not be completed. (Internal Server Error)'
According to your description, I assumed that you are using C# backend with SQL database. I would recommend that you could add the following code under the ConfigureMobileApp method of Startup.MobileApp.cs file for collecting the detailed error message.
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
Before inserting the new record to your table via the mobile client SDK, you could leverage the postman or fiddler to simulate the insert operation as follows to narrow this issue:
For more details about http table interface, you could refer to here.
Additionally, since you are adding your custom tables, please make sure you have manually updated your database to support your new database model or configure the Automatic Code First Migrations. For more details, you could refer to adrian hall's book about Implementing Table Controllers.

Extending Identity 2.1 to assign user to company and roles to users

Ok so i am really lost now,
I have an application that works great, i have groups of data that are available to a company subscription, When the user logs in they can access the information from the data that their roles allow, If they have multiple roles they have access to multiple areas of reports, if they only subscribe to one role they only have access to that one area. now all the data is the same and updated daily, so if 100 users are subscribed to roles 1,3 and 5 they can access that data. for the last decade the users of the companies would use one login to access the data ( one login supplied to company for George, But Lisa, john, Jerry and bob all use George's credentials to download reports). Note the data does not change per company and the site does not change per company all data and themes are part of the one site they have access to.
Now the Issue.
I have been asked by many of my users (Companies that access my data) that they would like to be able to to add users to their account so they can monitor who is downloading what report as i charge subscription by 500, 1000, and 2000 reports. My application records reports downloaded and the user can view these reports from their user portal and the id that downloaded it. the issue is that the companies want to be able to administer their own users, sort of like an account admin. I have looked into multi tenant and this does not really suit my needs, but from what i am looking at i feel i need to add a company_id to the user and company_id user roles so each company can have an admin role and a user role. but not sure as i study and read more on this i see allot of different ideas. but dont really see one that works for me.
here is an example.
Each user (company) has many users, each user in that company has roles of Admin or user. Users (companies) have access to many areas that roles allow them and the users of that company would have the same access to reports tha company roles provide. (or see aggregate data across all groups they belong to).
Thinking of adding this to IdentityModels:
public class ApplicationUserRole : IdentityUserRole<string>
{
public string CompanyId { get; set; }
}
public class ApplicationUser : IdentityUser<string, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>//, IAppUser
{
public ApplicationUser()
{
this.Id = Guid.NewGuid().ToString();
}
public virtual string CompanyId { get; set; }
public virtual List<CompanyEntity> Company { get; set; }
public DateTime CreatedOn { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(ApplicationUserManager manager, string authenticationType)
{
var userIdentity = await manager.CreateIdentityAsync(this, authenticationType);
return userIdentity;
}
}
add to IdentityConfig:
public string GetCurrentCompanyId(string userName)
{
var user = this.FindByName(userName);
if (user == null)
return string.Empty;
var currentCompany = string.Empty;
if (user.Claims.Count > 0)
{
currentCompany = user.Claims.Where(c => c.ClaimType == ConcordyaPayee.Core.Common.ConcordyaClaimTypes.CurrentCompanyId).FirstOrDefault().ClaimValue;
}
else
{
currentCompany = user.CurrentCompanyId;
}
return currentCompany;
}
public override Task<IdentityResult> AddToRoleAsync(string userId, string role, string companyId)
{
return base.AddToRoleAsync(userId, role);
}
Now im not sure about this, do i need to create a new user store. and how would authentication work, will i need to write a new Authorize Attribute. should i be using the claims. or maybe ACL system. or do i need to find a different identity provider.
Any help would be appreciated greatly. as i am getting very confused.

Can ASP Identity handle multitenancy, I have collisions in role names in ASP Identity web app

In a public facing web app multi tenant app, I'm using ASP Identity 2.x.
I let 3rd party's e.g. non-profits/comps self-register, create and populate their own roles and users. The code below is fine, if its an intranet scenarios where everyone belongs to the same company, it does not for work multiple non-profits
The non-profits registrants (understandably so) are naming roles with the same names, i.e. Managers and Employees etc. which are common/same across the database.
How can I extend ASP Identity to separate the roles per organization in a multi-tenant fashion, can you help me understand the design and how extend this, do I need a sub-role? i.e.
what do I do to ensure roles are scoped per organization at the database, so that different org's can have the same role names?
and, what do I do at the middle tier, i.e. usermanager, role manager objects level (middle tier)
//Roles/Create
[HttpPost]
public ActionResult Create(FormCollection form)
{
try
{
context.Roles.Add(new Microsoft.AspNet.Identity.EntityFramework.IdentityRole()
{ \\ Question - can I add another level here like company??
Name = form["RoleName"]
});
context.SaveChanges();
ViewBag.ResultMessage = "Role created successfully";
return RedirectToAction("RoleCreated");
}
catch
{
return View();
}
}`
Question- When adding a role, how do I separate the role to know which Role is from Which company when I add to the user?
public ActionResult RoleAddToUser(string UserName, string RoleName)
{
ApplicationUser user = context.Users.Where(u => u.UserName.Equals(UserName, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
var account = new AccountController();
account.UserManager.AddToRole(user.Id, RoleName);
ViewBag.ResultMessage = "Role created successfully !";
// prepopulat roles for the view dropdown
var list = context.Roles.OrderBy(r => r.Name).ToList().Select(rr => new SelectListItem { Value = rr.Name.ToString(), Text = rr.Name }).ToList();
ViewBag.Roles = list;
return View("ManageUserRoles");
}
how do I get the list of Roles and Users for that non-profit?
public ActionResult ManageUserRoles()
{
var list = context.Roles.OrderBy(r => r.Name).ToList().Select(rr => new SelectListItem { Value = rr.Name.ToString(), Text = rr.Name }).ToList();
ViewBag.Roles = list;
return View();
}`
I'm guessing you do have some sort of TenantId or CompanyId that is an identifier to the tenant you are working with.
IdentityRole and IdentityUser are framework objects that is recommended to inherit to add your own properties. You should do just that:
public class MyApplicationRole : IdentityRole
{
public int CompanyId { get; set; }
}
public class MyApplicationuser : IdentityUser
{
public int CompanyId { get; set; }
}
You can also add reference to a company object here and link it as a foreign key. This might make your life easier retrieving company objects related to the user.
Based on the objects above I'll try answering your questions:
When roles are created you can append a CompanyId to the name as a prefix. Something like <CompanyId>#CustomerServiceRole. This will avoid name clashes. At the same time add CompanyId to the MyApplicationRole class. Prefixed identifier will avoid clashes, identifier in the object will make the roles discoverable by the company. Alternatively you can implement RoleValidator and do the validation based on unique role name within a company. But then you will have to change the code that creates user identity when users are logged in, as role ids are not stored into the cookie.
I think this is self explanatory from the previous answer:
context.Roles.Add(new Microsoft.AspNet.Identity.EntityFramework.IdentityRole()
{
CompanyId = companyId, // TODO Get the company Id
Name = companyId.ToString() + "#" + form["RoleName"]
});
Self explanatory, see code snippets above.
Depending how you link your roles to companies the query will be different. Basically you'll need to do a join between companies and matching roles based on CompanyId and then filter companies by the non-profit flag.

How can I manage the table I added to my WAMS, or associate my WAMS with existing SQL DB tables?

I allowed the WAMS wizard to create the "test" table ("Items") it so congenially offers to create after setting up my WAMS.
Then I wanted to create a table that will actually be useful to me. The instructions in the wizard do say, "You can add and remove tables later by using the "Data" tab above."
So I did that, and I did create a table, but I can't see now where I can change the structure of the table (IOW, add columns). I've tried 2-clicking the service, 2-clicking the table, selecting the "New" button, right-clicking on the sole column name (id) in my table, etc., but all to no avail.
Something I'm confused about, too, is the relationship of the tables I create this way with my existing SQL DB tables - or can I do without that SQL DB now (once I get these WAMSical tables set up)?
Or why can't I associate my existing SQL DB tables to my WAMS? And if I can -- how?
UPDATE
Also, it seems there is a mismatch between what is written and what I'm actually experiencing. This (from http://msdn.microsoft.com/en-us/magazine/jj721590.aspx) is not true/did not happen for me:
"2.Create a relational table to store your data. When you click on the Create TodoItem Table button, the wizard will automatically create a table based on the Windows Azure SQL Database you created (or re-used) previously."
I tried "from scratch," creating a new WAMS. Again, I get when I select my existing SQL DB, "Database and Mobile Service Not in the Same Region - Performance will decrease...Additionally, the data sent from the db to the mobile service will be counted as billable bandwidth usage. We recommend that you choose a database in the same location as the mobile service."
I would like to, but how? Why didn't WAMS adjust this for me automatically - or at least give me the option to put my DB and Mobile Service in the same place?
UPDATE 2
What is interesting is that I CAN see the new tables in LINQPad. I already had two SQL DB tables that display under that connection info, but on the same level as those tables is my WAMS name, under which are the "default" Items table and one of my own I created (both of which, though, only have one column, specifically "Id (Int64)"
IOW, what I see in LINQPad is:
blaBlaBla.database.windows.net,1433.blaBla
BlaBla
BlaBlaSQLDB_Table1
BlaBlaSQLDB_Table2
wamsName
Items
Id (Int64)
BlaBlaWAMSTable
Id (Int64)
...so how do I extend/manage the "BlaBlaWAMSTable" is the problem now...
UPDATE 3
Well, looky here; LINQPad to the rescue again:
select * from <WAMSName>.<TableName>
...shows there is a record after I created the table I wanted via a class in my project (NOT in the Azure/WAMS management area)
...and, of course, LINQPad shows the newly added columns added that way.
All I needed to do was follow the steps provided (Reference the Azure SDK, add a corresponding using clause, etc.) and then added this method to test it out:
private async void InsertTestRecordIntoWAMSSQLDBTable()
{
<WAMS Table class name> invitation = new <WAMS Table class name> { SenderID = "donkeyKongSioux#supermax.gov", ReaderDeviceID = "00-AA-11-BB-01-AB-10-BA", ReaderName = "B. Clay Shannon", SenderUTCOffset = 5, SenderDeviceID = "BA-10-AB-01-BB-11-AA-00" };
await App.MobileService.GetTable<<WAMS Table class name>>().InsertAsync(invitation);
}
...and it worked. Now it would be nice to have some samples/examples for select queries as well as updates.
And my big remaining question (so far): can I decorate/annotate the columns/members of my table class? IOW, can I change this:
public class
{
public int Id { get; set; }
public string SenderID { get; set; }
public string ReaderDeviceID { get; set; }
public string ReaderName { get; set; }
public int SenderUTCOffset { get; set; }
public string SenderDeviceID { get; set; }
}
...to something like this:
public class
{
[Primary, AutoInc]
public int Id { get; set; }
[Indexed]
public string SenderID { get; set; }
[Unique]
public string ReaderDeviceID { get; set; }
[MaxLength(255)]
public string ReaderName { get; set; }
public int SenderUTCOffset { get; set; }
public string SenderDeviceID { get; set; }
}
?
I can't do exactly that, as those are SQLite annotations, but since I am unable to manage my table from the Azure/WAMS portal, how can I designate these attributes?
After altering the design of the table in code, I'm able to see that those columns have been added to my table in the WAMS portal, but it seems the only thing I can do to the columns is add an index...
UPDATE 4
It turns out that creating tables in WAMS is as easy as pie (but not as easy as pi/as hard as pi) - once you know how to do it.
With the WAMS created, select Data, and then Create to create a table. Give it a name, and select the permissions you want. This will give you a deadly dull but "living" database table with one, count 'em, one, column: ID, a BigInt, indexed.
THEN, to actually add more columns to the table, the easiest way I've found (the only way I've found that is, and it is easy) is to:
1) Create a class that corresponds to the database table, a la SQLite, such as:
public class WAMS_DUCKBILL
{
public int Id { get; set; }
public string PlatypusID { get; set; }
public DateTime UpdateTimeUTC { get; set; }
public double Income { get; set; }
public double Outgo { get; set; }
}
2) Write a method that will add a record to this table, such as:
private async void InsertTestRecordIntoWAMSDuckbillTable()
{
WAMS_DUCKBILL duckbill = new WAMS_DUCKBILL { PlatypusID = "42", UpdateTimeUTC =
DateTime.Now, Income = 3.85, Outgo = 8311.79 };
await MobileService.GetTable<WAMS_DUCKBILL>().InsertAsync(duckbill);
}
3) Call that method from App.xaml.cs' OnLaunched event
4) As can then be seen by running the following query in LINQPad (or however you want to pee[k,r] into the database):
SELECT * FROM platypi.WAMS_DUCKBILL
Call me old fashioned, but notice I'm using tired old SQL as opposed to LINQ here; so sue me. At any rate, LINQPad shows that the test record has indeed been inserted into the WAMS table. Voila! as the escargot-eating, beret-at-a-rakish-angle wearing cats say.
The focus for Windows Azure Mobile Services (WAMS) are app developers who does not want to invest a lot of time to develop a backend. Therefor the data model easier than you might think.
Tables in WAMS are always backed by tables in a SQL database. You can create and delete tables via the portal.
Creating columns is a bit different. You create columns by simply using them. As soon as you write data for a column that does not exist, WAMS creates that column automatically. That's called Dynamic Schema. After development you should disable Dynamic Schema. You find it in the Mobile Service, Configure.
Documentation: Data access in Windows Azure Mobile Services

Resources