When a user logins using the Auth0 lock on my client side, I get an idToken, but also an idTokenPayload which looks like this:
idTokenPayload = {
audience: "AUTH0CLIENTID",
exp: 1494190538,
iat: 1494154538,
iss: "AUTH0DOMAIN"
sub: "USERNAME"
};
Would it be possible to return the userId in Auth0's database instead of the username in the sub field?
The reason I want to do this is that I want to keep Auth0's db for users, and I have on my server-side some Profile, Post, Comment etc entities which have a userId column. Right now before each request on my entities I need to populate the user by doing an extra request: let id = Profile.find("... where username === auth0.sub").getId(); (pseudo-code of course).
With the C# lock sdk, you get back an Auth0User after the call to the LoginAsync method in the Auth0 client. Let's call this variable auth0User. If I look at auth0User.Profile, a JObject (it's a JSON object if you're not using C#), it contains a JSON array named "identities". My identities variable initialization looks like:
var identities = (JArray)auth0User.Profile["identities"];
This array contains all the identity providers associated with the user. If like me you haven't attached any other sign in besides Auth0, there will be just 1 entry here. Each object in this JSON array will contain a "provider" string and a "user_id" string. If the provider says "auth0" then it's from Auth0. Since I don't use FB or other account types I'm not exactly sure what they say. Here's my C# code to get the UserID:
var identities = (JArray)auth0User.Profile["identities"];
if (identities != null)
{
foreach (var identity in identities)
{
var provider = (string)identity["provider"];
if (string.Equals(provider, "auth0"))
{
UserID = (string)identity["user_id"];
break;
}
}
}
I believe that this should all be provided standard without needing to add any rules or webhooks. This article should explain in more detail and also gives examples in javascript: auth0 normalized user profile
Related
I recently started developing using IdentityServer4. What I want to achieve is to have a number of independent web applications that use the same authorization server, my identity server.
My problem is how to make sure, that all my independend web applications have obtained and display the up to date user information (like firstName,lastName,avatar etc) which are stored in my IdentityServer4 database
I am aware that I should implement the IProfileService interface, to make sure that user-info endpoint will return all additional user info, but I dont know where to call this api request from my web applications. I have created a function that looks like this:
var t = await HttpContext.GetTokenAsync("access_token");
if (!String.IsNullOrEmpty(t))
{
var client = new HttpClient();
var userInfoRequest = new UserInfoRequest()
{
Address = "https://localhost:5001/connect/userinfo",
Token = t
};
var response = client.GetUserInfoAsync(userInfoRequest).Result;
if (response.IsError)
throw new Exception("Invalid accessToken");
dynamic responseObject = JsonConvert.DeserializeObject(response.Raw);
string firstName = responseObject.FirstName.ToString();
HttpContext.Session.SetString("User_FirstName", firstName);
string lastName = responseObject.LastName.ToString();
HttpContext.Session.SetString("User_LastName", lastName);
HttpContext.Session.SetString("User_FullName", firstName + " " + lastName);
if (responseObject.Image != null && !String.IsNullOrEmpty(responseObject.Image.ToString()))
{
string im = responseObject.Image.ToString();
HttpContext.Session.SetString("User_Image", im);
}
}
to get user Info from web applications.
My problem is when and how to call this function, every time the user redirects logged in from identity server to my web application, and how to make sure that Sessions will keep user associated data, for as much as the user remains logged in to my web application.
You can call Token Introspection endpoint to get all user info from #identityServer4.
In a ASP.NET Core application with Azure AD Connected and Configured. I am able to get the NameIdentifier using this code:
var user = User.FindFirst(ClaimTypes.NameIdentifier).Value; ✔️
When trying to get just a name with the following line of code:
var user = User.FindFirst(ClaimTypes.Name).Value; ❌
I receive the following error:
Object reference not set to an instance of an object.
Looked up in Azure Active Directory the user does have a full first and last name. Tried many examples online, it looks like the name should be showing.
UPDATE:
Finally figured it out, at first I managed to get all the human readable text like this:
foreach (Claim item in User.Claims)
{
if (item.Type.Contains("name"))
{
var username = item.Value;
}
}
this is much better
var userName = User.FindFirst("name").Value;
According to document : https://learn.microsoft.com/en-us/azure/architecture/multitenant-identity/claims :
In ASP.NET Core, the OpenID Connect middleware converts some of the claim types when it populates the Claims collection for the user principal:
oid > http://schemas.microsoft.com/identity/claims/objectidentifier
tid > http://schemas.microsoft.com/identity/claims/tenantid
unique_name >
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
upn > http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn
So that if the unique_name exists in ID token , the middleware will populate the value of ClaimTypes.Name . But according to document : Microsoft identity platform ID tokens:
unique_name : Provides a human readable value that identifies the subject of the token. This value isn't guaranteed to be unique within a tenant and should be used only for display purposes. Only issued in v1.0 id_tokens.
So that the claim only issued in Azure AD V1.0 id tokens ,and it isn't guaranteed to be unique within a tenant .
You can get the user's name and email by (The profile scope is required to receive the claims & test with Azure AD V2.0 app):
var userName = User.FindFirst("name").Value;
var Email = User.FindFirst("preferred_username").Value;
If you want to know the first name and last name of current user , you may acquire access token for Microsoft Graph , call user endpoint with token and check the givenName/surname values from response :
https://learn.microsoft.com/en-us/graph/api/user-get?view=graph-rest-1.0&tabs=http
I struggled with the same issue and found an example and it works very well:
Basically:
public WeatherForecastController(ILogger<WeatherForecastController> logger, ITokenAcquisition tokenAcquisition, GraphServiceClient graphServiceClient, IOptions<MicrosoftGraphOptions> graphOptions)
{
_logger = logger;
_tokenAcquisition = tokenAcquisition;
_graphServiceClient = graphServiceClient;
_graphOptions = graphOptions;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
User user = _graphServiceClient.Me.Request().GetAsync().GetAwaiter().GetResult();
_logger.LogInformation("User Id: " + user.Id.ToString());
This link will give the details on how to use it with Azure AD, .net web api, and angular
active-directory-dotnet-native-aspnetcore-v2
My environment is ASP.NET Core 2.x accessing CosmosDb (aka DocumentDb) with the .NET SDK.
The default consistency level of my collection is set to "Session". For my use-case I need a single authenticated web user to always have consistent data in terms of reads/writes between web requests.
I have some CosmosDB Repository logic that is made available to my controller logic via ASP.NET Core Singleton dependency injection as such:
services.AddSingleton<DocumentDBRepository, DocumentDBRepository>(x =>
new DocumentDBRepository(
WebUtil.GetMachineConfig("DOCDB_ENDPOINT", Configuration),
WebUtil.GetMachineConfig("DOCDB_KEY", Configuration),
WebUtil.GetMachineConfig("DOCDB_DB", Configuration),
"MyCollection",
maxDocDbCons));
DocumentDBRespository creates a cosmos client like so:
public DocumentDBRepository(string endpoint, string authkey, string database, string collection, int maxConnections)
{
_Collection = collection;
_DatabaseId = database;
_Client = new DocumentClient(new Uri(endpoint), authkey,
new ConnectionPolicy()
{
MaxConnectionLimit = maxConnections,
ConnectionMode = ConnectionMode.Direct,
ConnectionProtocol = Protocol.Tcp,
RetryOptions = new RetryOptions()
{
MaxRetryAttemptsOnThrottledRequests = 10
}
});
_Client.OpenAsync().Wait();
CreateDatabaseIfNotExistsAsync().Wait();
CreateCollectionIfNotExistsAsync().Wait();
}
As far as I understand that means one CosmosDB client per Web App server. I do have multiple web app servers, so a single user might hit the CosmosDB from multiple AppServers and different CosmosDb clients.
Before a user interacts with the ComosDB, I check their session object for a CosmosDb SessionToken, like so:
string docDbSessionToken = HttpContext.Session.GetString("StorageSessionToken");
Then, when writing a document for example, the method looks something like so:
public async Task<Document> CreateItemAsync<T>(T item, Ref<string> sessionTokenOut, string sessionTokenIn = null)
{
ResourceResponse<Document> response = null;
if (string.IsNullOrEmpty(sessionTokenIn))
{
response = await _Client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(_DatabaseId, _Collection), item);
}
else
{
response = await _Client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(_DatabaseId, _Collection), item, new RequestOptions() { SessionToken = sessionTokenIn });
}
sessionTokenOut.Value = response.SessionToken;
Document created = response.Resource;
return created;
}
The idea being that if we have a session token, we pass one in and use it. If we don't have one, just create the document and then return the newly created session token back to the caller. This works fine...
Except, I'm unclear as to why when I do pass in a session token, I get a DIFFERENT session token back. In other words, when _Client.CreateDocumentAsync returns, response.SessionToken is always different from parameter sessionTokenIn.
Does that mean I should be using the new session token from that point on for that user? Does it mean I should ignore the new session token and use the initial session token?
How long do one of these "sessions" even last? Are they sessions in the traditional sense?
Ultimately, I just need to make sure that the same user can always read their writes, regardless of which AppServer they connect with or how many other users are currently using the DB.
I guess the confusion here is on what a session is?
In most scenarios/frameworks treat session as static identifier (correlation), where as with cosmos the sessionToken is dynamic (kind of bookmark/representation of cosmos db state, which changes with writes). Naming it as 'sessionToken' might be root of the confusion.
In this specific scenario, you should use the "returned sessiontoken" from cosmos API's.
I'm trying to see if it's possible in an ASP.NET-Core 2 web app, that if a User is authenticated in a request, we can also check in some Filter/ActionMethod Attribute:
They have a specific claim
The route has an string id segment (e.g. HttpPut[("{id}")] ) and that id segment needs to match the Auth'd User's Id.
Request includes a JWT header with the bearer token in it, which is used to 'create' the Authenticated Identity (which works 100% fine).
e.g.
HTTP PUT /accounts/PureKrome | User Id:PureKrome | Claim: Irrelivant. => Can continue. [You are updating yourself. Don't need any special claim when updating yourself].
HTTP PUT /accounts/PureKrome | User is Anonymous or Id:SomethingElse | Claim: irrelivant => Failure (Forbidden response) [Someone else is trying to update you and doesn't have the correct overriding claim. So fail]
HTTP PUT /accounts/SomeoneElse | User is Id:PureKrome | Claim: correct claim. => Can continue [Trying to update a different user BUT you have a claim that allows you to do that]
Right now, I do this in my ActionMethod code ... one of the first things. So I was just curious to see if this could be achieved using an Attribute that decorates the ActionMethod, instead.
That isn’t actually too complicated. All you need to do is have an authorization filter that looks at the route values and then checks it with the current user.
Something simple like this should already work fine:
public class ValidateUserIdRouteAttribute : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var user = context.HttpContext.User;
var requestedUserId = context.RouteData.Values["id"] as string;
var currentUserId = user.FindFirstValue(ClaimTypes.NameIdentifier);
if (requestedUserId != currentUserId &&
!user.HasClaim(c => c.Type == "may-edit" && c.Value == requestedUserId))
{
context.Result = new UnauthorizedResult();
}
}
}
And used on a route it would look like this:
[ValidateUserIdRoute]
[HttpGet("/account/update/{id}")]
public IActionResult UpdateAccount(string id)
{
// …
}
That’s all. If you have authentication set up properly, the Bearer token will be used to authenticate the user which may or may not set up the claims properly, and then you just check against those claims to see if accessing the route is allowed or not.
Of course, you can expand on this idea and add some more functionality to it, e.g. support different route data keys or something like that.
I am trying to learn the new webapi2.1 authentication pieces.
I have got the bearer token wired up and working with my webapi. My next thing I would like to do is be able to store some additional information within the token (if possible) so when the client sends back the token I can retrieve the details without the need of them sending multiple values.
Can the token be extended to contain custom data?
Sorry if the question is a little vague but I have had a big hunt around and can't seem to find any further information
Thank you
Since the token is signed with a "secret" key - only the issuer can add data to it.
You can amend something to the claim set after receiving the token in your Web API - this is called claims transformation.
I have a sample of it here:
https://github.com/thinktecture/Thinktecture.IdentityModel/tree/master/samples/OWIN/AuthenticationTansformation
In essence you are writing some code that inspects the incoming token and add application specific claims to the resulting principal.
// Transform claims to application identity
app.UseClaimsTransformation(TransformClaims);
private Task<ClaimsPrincipal> TransformClaims(ClaimsPrincipal incoming)
{
if (!incoming.Identity.IsAuthenticated)
{
return Task.FromResult<ClaimsPrincipal>(incoming);
}
// Parse incoming claims - create new principal with app claims
var claims = new List<Claim>
{
new Claim(ClaimTypes.Role, "foo"),
new Claim(ClaimTypes.Role, "bar")
};
var nameId = incoming.FindFirst(ClaimTypes.NameIdentifier);
if (nameId != null)
{
claims.Add(nameId);
}
var thumbprint = incoming.FindFirst(ClaimTypes.Thumbprint);
if (thumbprint != null)
{
claims.Add(thumbprint);
}
var id = new ClaimsIdentity("Application");
id.AddClaims(claims);
return Task.FromResult<ClaimsPrincipal>(new ClaimsPrincipal(id));
}