My current implementation uses OWIN token implementation using the standard functionality using endpoints:
e.g /token endpoint and with the below method
and then using:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
authentication code + claim assignment
context.Validated(ticket);
}
I am trying to impersonate a user. Ideally i would like to be able to recall / re-run my code in the GrantResourceOwnerCredentials but this only seems to be run with /token endpoint. Or find a way to regenerate the token claims and send those to the user manually in my own endpoint e.g /tokenimpersonate method?
I do not use cookies this is a pure token implementation.
The other alternative is that i could adjust the claims on an existing user but my understanding i need to log them out and log them in, in this case how do i pass a new token to the front-end?
This is the code i eventually used to make this work:
Authentication.SignOut(authTypeNames.ToArray());
var oAuthIdentity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, dbUser.Username));
oAuthIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, dbUser.User_ID.ToString()));
oAuthIdentity.AddClaim(new Claim(ClaimTypes.Role, dbUser.UserRole));
oAuthIdentity.AddClaim(new Claim(ClaimTypes.Role, dbUser.User_ID.ToString()));
//ads only certain docadmin ids to the role.
if (dbUser.UserRole == Medapp.BusinessFacade.Constants.ROLE_SECRETARY)
{
// /doc/home
//add guids of all the doctors as roles
var roles = db.OfficeAdministrators.Where(p => p.Admin_ID == dbUser.User_ID);
foreach (var role in roles)
{
oAuthIdentity.AddClaim(new Claim(ClaimTypes.Role, role.Doctor_ID.ToString()));
}
}
List<Claim> jroles = oAuthIdentity.Claims.Where(c => c.Type == ClaimTypes.Role).ToList();
AuthenticationProperties properties = CreateProperties(dbUser.User_ID.ToString(), dbUser.UserRole, dbUser.Username, Newtonsoft.Json.JsonConvert.SerializeObject(jroles.Select(x => x.Value))); //user.UserName);
properties.IsPersistent = true;
properties.ExpiresUtc = new System.DateTimeOffset(new DateTime().AddDays(365), new System.TimeSpan());
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
DateTime currentUtc = DateTime.UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromDays(365));
string accessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
JObject token = new JObject(
new JProperty("username", dbUser.Username),
new JProperty("token", accessToken),
new JProperty("uid", dbUser.User_ID.ToString()),
new JProperty("type", dbUser.UserRole),
new JProperty("roles", Newtonsoft.Json.JsonConvert.SerializeObject(jroles.Select(x => x.Value))),
new JProperty("access_token", accessToken),
new JProperty("token_type", "bearer"),
new JProperty("expires_in", TimeSpan.FromDays(365).TotalSeconds.ToString()),
new JProperty("issued", currentUtc.ToString("ddd, dd MMM yyyy HH':'mm':'ss 'GMT'")),
new JProperty("expires", currentUtc.Add(TimeSpan.FromDays(365)).ToString("ddd, dd MMM yyyy HH:mm:ss 'GMT'"))
);
return Ok(token);
I think the best way to do that is to implement refresh token in your token-based authentication.
For better understanding take a look at this page:
http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/
Related
As a follow-up on my question about how to setup a ROPC Flow. I want to access my API through the ROPC flow (currently using default user flows) and also through my web app which uses a custom policy on sign-in. This results in two different access tokens. On the left is access token received using the AcquireTokenSilent call and on the right is the access token received through postman with ROPC.
The custom policy token (on the left) gives an "Authorization has been denied for this request." error, while the token on the right is fine. I am assuming that the custom policy token does not work because it does not contain the tfp claim (and if it did, it would be a different one).
How can I set it up so that I can still use the ROPC flow while also using the custom policy? I would like to keep the current userjourney in the custom policy the same. Although if it is possible to somehow add ROPC as an option to it, then it would be fine.
Based on the description above, you are using two policy types - a user flow and a custom policy. And, you are attempting to get SSO between the two.
This is not a supported scenario. This is because the token uses different keys that signs the token.
If custom policies are required for your scenario, I suggest converting the user flow ROPC to a custom policy using this document https://learn.microsoft.com/en-us/azure/active-directory-b2c/add-ropc-policy?tabs=app-reg-ga&pivots=b2c-custom-policy
So I finally found a way to do this in .NET Framework, if you want a solution for .NET Core you would sadly have to look somewhere else.
In your startup add the following.
/*
* Configure the authorization OWIN middleware
*/
private void ConfigureAuthenticationAzure(IAppBuilder app)
{
app.UseOAuthBearerAuthentication(CreateOptions(ClientId, SignUpSignInPolicy, azureDiscoveryEndpoint));
app.UseOAuthBearerAuthentication(CreateOptions(ClientId, ApiPolicy, azureDiscoveryEndpointAPI));
}
private OAuthBearerAuthenticationOptions CreateOptions(string audience, string policy, string discoveryEndpoint)
{
var metadataEndpoint = String.Format(discoveryEndpoint, Tenant, policy);
// This is the default check, in OnValidateIdentity, we check for more.
TokenValidationParameters tvps = new TokenValidationParameters
{
// This is where you specify that your API only accepts tokens from its own clients
ValidAudience = ClientId,
ValidateAudience = true,
AuthenticationType = policy,
NameClaimType = "http://schemas.microsoft.com/identity/claims/objectidentifier",
ValidateIssuer = true,
};
return new OAuthBearerAuthenticationOptions
{
AccessTokenFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider(metadataEndpoint)),
Provider = new OAuthBearerAuthenticationProvider
{
OnValidateIdentity = async context =>
{
try
{
var authorizationHeader = context.Request.Headers.Get("Authorization");
var userJwtToken = authorizationHeader.Substring("Bearer ".Length).Trim();
var ticket = context.Ticket;
//var identity = ticket.Identity;
var jwtSecurityToken = new JwtSecurityToken(userJwtToken);
var expiration = jwtSecurityToken.ValidTo.ToLocalTime();
if (expiration < DateTime.Now)
{
log.Warn("The JWT token has expired.");
context.Rejected();
return;
}
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(discoveryEndpoint, new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration openIdconfig = configManager.GetConfigurationAsync().Result;
var validationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKeys = openIdconfig.SigningKeys,
ValidateIssuer = true,
ValidIssuer = $"{AzureIssuer.ToLower()}/v2.0/",
ValidateAudience = true,
ValidAudience = audience,
ValidateLifetime = true,
//ClockSkew = TimeSpan.Zero
};
var handler = new JwtSecurityTokenHandler();
SecurityToken securityToken;
var principal = handler.ValidateToken(userJwtToken, validationParameters, out securityToken);
var policyName = principal.FindFirst("tfp")?.Value;
// Add the name claim type for this authentication type
if (policyName.ToLower() == DefaultPolicy.ToLower()) // Sign In Only policy...
{
// Run specific code here for the policy that just sent a token back to the application...
context.Validated(ticket);
return;
}
else if (policyName.ToLower() == SignUpSignInPolicy.ToLower())
{
context.Validated(ticket);
return;
}
context.Rejected();
return;
}
catch(Exception ex)
{
context.Rejected();
return;
}
}
}
};
}
I am trying to create multiple VMs to multiple Subscriptions programmatically. So I need to list all subscriptions that I can access. But I cannot grant permissions to registered app, so I have to use my own Azure credential.
Then I tried
var subscriptionClient = new Microsoft.Azure.Management.ResourceManager.Fluent.SubscriptionClient(new DefaultAzureCredential());
and
var subscriptionClient = new Microsoft.Azure.Management.ResourceManager.Fluent.SubscriptionClient(new UserPasswordCredential(username,password));
but none of them compiles.
The answer of question How to list subscriptions with Microsoft.Azure.ResourceManager? is almost the answer of my question, but I cannot add comment to ask more question about it.
I installed Microsoft.IdentityModel.Clients.ActiveDirectory version 3.13.2.870 and tried:
var ctx = new AuthenticationContext("https://login.microsoftonline.com/common");
but ctx doesn't have AcquireToken, it only has AcquireTokenAsync. Unfortunately the following code still doesn't work
var mainAuthRes = await context.AcquireTokenAsync(m_resource, m_clientId, new Uri(m_redirectURI), PromptBehavior.Always);
The compiler says the fourth parameter is wrong which means
context.AcquireTokenAsync(string resource, string client , Uri uri , PromptBehavior promptBehavior )
is not a valid method.
Is there any way to list subscriptions with my current azure credential (without registering app) using C#?
Try the code works for me, it uses the VisualStudioCredential of Azure.Identity to auth, it will list all the subscriptions in all the AAD tenants that you can access(the user account logged in VS).
using Azure.Core;
using Azure.Identity;
using Microsoft.Azure.Management.ResourceManager;
using Microsoft.Rest;
using System;
using System.Threading;
namespace ConsoleApp2
{
class Program
{
public static void Main(string[] args)
{
VisualStudioCredential tokenCredential = new VisualStudioCredential();
TokenRequestContext requestContext = new TokenRequestContext(new string[] { "https://management.azure.com" });
CancellationTokenSource cts = new CancellationTokenSource();
var accessToken = tokenCredential.GetToken(requestContext, cts.Token);
ServiceClientCredentials serviceClientCredentials = new TokenCredentials(accessToken.Token);
SubscriptionClient SubscriptionClient = new SubscriptionClient(serviceClientCredentials);
var tenants = SubscriptionClient.Tenants.List();
foreach (var tenant in tenants)
{
//Console.WriteLine(tenant.TenantId);
VisualStudioCredentialOptions visualStudioCredentialOptions = new VisualStudioCredentialOptions{ TenantId = tenant.TenantId };
VisualStudioCredential tokenCredential1 = new VisualStudioCredential(visualStudioCredentialOptions);
TokenRequestContext requestContext1 = new TokenRequestContext(new string[] { "https://management.azure.com" });
CancellationTokenSource cts1 = new CancellationTokenSource();
var accessToken1 = tokenCredential1.GetToken(requestContext, cts1.Token);
ServiceClientCredentials serviceClientCredentials1 = new TokenCredentials(accessToken1.Token);
SubscriptionClient SubscriptionClient1 = new SubscriptionClient(serviceClientCredentials1);
var subs = SubscriptionClient1.Subscriptions.List();
foreach (var sub in subs)
{
//Console.WriteLine(sub.DisplayName);
Console.WriteLine($"SubscriptionName : {sub.DisplayName}");
Console.WriteLine($"SubscriptionId : {sub.SubscriptionId}");
Console.WriteLine($"TenantId : {tenant.TenantId}");
Console.WriteLine($"State : {sub.State}");
Console.WriteLine();
}
}
}
}
}
You can try using the REST API Subscriptions - List.
GET https://management.azure.com/subscriptions?api-version=2020-01-01
Request Header:
Authorization: Bearer <access token>
Update:
The simplest way to get access token is open the link Subscriptions - List and sign in with your account.
Then click "try it" at the top right of the script.
You will find the access token in Request preview.
So, ive made a simple basic auth middleware for owin. The middleware returns an AuthenticationTicket with the user principal claims, authentication type ("Basic") yada yada.
protected override Task<AuthenticationTicket> AuthenticateCoreAsync()
{
....
var claimsIdentity = TryGetPrincipalFromBasicCredentials(token, Options.CredentialValidationFunction);
....
var ticket = new AuthenticationTicket(claimsIdentity, new AuthenticationProperties(){userName = claimsIdentity.name});
return Task.FromResult(ticket);
}
To get the claims, im using a hardcoded value for testing:
var claims = new List<Claim>();
claims.Add(new Claim(KneatClaimTypes.Permission, Permission.UserAdministrationView.ToString()));
var claimsIdentity = new ClaimsIdentity(
new GenericIdentity("Administrator", "Basic"),
claims
);
return claimsIdentity;
I`m hooking into owin using a simple extension using the same architecture IdentityModel has
https://github.com/IdentityModel/IdentityModel.Owin.BasicAuthentication
app.UseBasicAuthentication(new BasicAuthOptions("Realm",
(username, password, context) => BasicAuthCredentialCheck.Authenticate(username, password, context))
{
AuthenticationMode = AuthenticationMode.Active,
AuthenticationType = "Basic"
});
Im creating the correct principal for the ticket, but after the owin resolves the authentication the Thread.currentPrincipal is not populated. If i add a simple cookie auth , whenever i call Owin.Authentication.SignIn(...) it creates the Principal for me. If i set manually the Thread.currentPrincipal, something is UNSETTING that.
Im not very deep on how owin works under the hood, and i was tryng to get the source from it and begin reading it maybe ill figure it out. But in case i dont, any ideas on who could be resetting this Thread.currentPrincipal ? (Searched my own app code, no one does that, only reads no setting)
Best Regards.
I have a .NET Web API that I am using to do some interaction with Microsoft Graph and Azure AD. However, when I attempt to create an extension on the user, it comes back with Access Denied.
I know it is possible from the documentation here however, it doesnt seem to work for me.
For the API, I am using client credentials. So my web app authenticates to the API using user credentials, and then from the API to the graph it uses the client.
My app on Azure AD has the Application Permission Read and Write Directory Data set to true as it states it needs to be in the documentation for a user extension.
I know my token is valid as I can retrieve data with it.
Here is my code for retrieving it:
private const string _createApprovalUrl = "https://graph.microsoft.com/beta/users/{0}/extensions";
public static async Task<bool> CreateApprovalSystemSchema(string userId)
{
using(var client = new HttpClient())
{
using(var req = new HttpRequestMessage(HttpMethod.Post, _createApprovalUrl))
{
var token = await GetToken();
req.Headers.Add("Authorization", string.Format("Bearer {0}", token));
req.Headers.TryAddWithoutValidation("Content-Type", "application/json");
var requestContent = JsonConvert.SerializeObject(new { extensionName = "<name>", id = "<id>", approvalLimit = "0" });
req.Content = new StringContent(requestContent, Encoding.UTF8, "application/json");
using(var response = await client.SendAsync(req))
{
var content = await response.Content.ReadAsStringAsync();
ApprovalSystemSchema schema = JsonConvert.DeserializeObject<ApprovalSystemSchema>(content);
if(schema.Id == null)
{
return false;
}
return true;
}
}
}
}
Is there anyone who may have a workaround on this, or information as to when this will be doable?
Thanks,
We took a look and it looks like you have a bug/line of code missing. You appear to be making this exact request:
POST https://graph.microsoft.com/beta/users/{0}/extensions
Looks like you are missing the code to replace the {0} with an actual user id. Please make the fix and let us know if you are now able to create an extension on the user.
Here is my scenario. I have an MVC 5 application that uses Owin as an authentication mechanism. The default template calls the SignInManager.PasswordSignInAsync in the Login action which I would like to overwrite to use LDAP to validate the user instead of looking into the database.
I am able to do the validation via:
PrincipalContext dc = new PrincipalContext(ContextType.Domain, "domain.com", "DC=domain,DC=com", "user_name", "password");
bool authenticated = dc.ValidateCredentials(userName, password);
Then I can retrieve the UserPrincipal using:
UserPrincipal user = UserPrincipal.FindByIdentity(dc, IdentityType.SamAccountName, userName);
However, I am stuck here and I am not sure how to continue with signing in the user. The goal is that after I sign in the user, I would have access to User.Identity including all the roles the user is in. Essentially, the app should behave as if it uses Windows Authentication, but the credentials are provided by the user on the Login page.
You would probably ask why not user Windows Authentication directly. The app will be accessed from the outside of the network, but the requirements are to use AD authentication and authorization. Hence my predicament.
Any suggestions are highly appreciated.
Thank you.
After many hours of research and trial and error, here is what I ended up doing:
AccountController.cs - Create the application user and sign in
ApplicationUser usr = new ApplicationUser() { UserName = model.Email };
bool auth = await UserManager.CheckPasswordAsync(usr, model.Password);
if (auth)
{
List claims = new List();
foreach (var group in Request.LogonUserIdentity.Groups)
{
string role = new SecurityIdentifier(group.Value).Translate(typeof(NTAccount)).Value;
string clean = role.Substring(role.IndexOf("\\") + 1, role.Length - (role.IndexOf("\\") + 1));
claims.Add(new Claim(ClaimTypes.Role, clean));
}
claims.Add(new Claim(ClaimTypes.NameIdentifier, model.Email));
claims.Add(new Claim(ClaimTypes.Name, model.Email));
ClaimsIdentity ci = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties()
{
AllowRefresh = true,
IsPersistent = false,
ExpiresUtc = DateTime.UtcNow.AddDays(7),
}, ci);
return RedirectToLocal(returnUrl);
}
else
{
ModelState.AddModelError("", "Invalid login credentials.");
return View(model);
}
IdentityConfig.cs (CheckPasswordAsync) - Authenticate against LDAP
public override async Task CheckPasswordAsync(ApplicationUser user, string password)
{
PrincipalContext dc = new PrincipalContext(ContextType.Domain, "domain", "DC=domain,DC=com", [user_name], [password]);
bool authenticated = dc.ValidateCredentials(user.UserName, password);
return authenticated;
}
Global.asax - if you are using the Anti Forgery Token in your login form
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
At this point, you will are logged in and can access the User.Identity object. You can also mark controllers and actions with [Authorize(Roles = "some_role"]
It turned out that it was easier than I thought, it is just that not much is really written on the topic (at least I could not find anything).
Also, this code presumes that you are running the app from a server which has access to the Domain Controller on your network. If you are on a DMZ server, you need to discuss this strategy with your network admin for other options.
I hope this saves you some time. I am also eager to hear what the community thinks of this. Maybe there is a better way of handling this situation. If so, please share it here.
Thanks.
Daniel D.