I am using this package https://www.nuget.org/packages/Microsoft.Identity.Client
to authenticate a user, auth is working fine, problem is, I was thinking to use the token that I get after login to get the user name and email (as I need not only access to the inbox, contacts, and calendar; but also link the user to a rol using an email).
The problem is, when I get the token, I get a long string as userId (I guess encrypted). Is there any way I can use this package to get the email?
This is the section where I get the token back
public SessionTokenCache(string userId, HttpContextBase httpContext)
{
this.userId = userId;
cacheId = userId + "_TokenCache";
this.httpContext = httpContext;
BeforeAccess = BeforeAccessNotification;
AfterAccess = AfterAccessNotification;
Load();
}
This is the tutorial I followed
https://dev.outlook.com/restapi/tutorial/dotnet
Once you have a token, maybe you can use the Graph API to get details for the logged on user? The result is Json which can be used to extract the bits you want.
public static async Task<string> GetUserData(string token)
{
//get data from API
HttpClient client = new HttpClient();
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/me");
message.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", token);
HttpResponseMessage response = await client.SendAsync(message);
string responseString = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
return responseString;
else
return null;
}
Related
I have one method for login as per below :
public async Task<ActionResult> Index(LoginModel loginModel)
{
string url = "API_URL";
var response = await client.PostAsJsonAsync(url, loginModel);
if (response.IsSuccessStatusCode)
{
string result = await response.Content.ReadAsStringAsync();
var jsonData = JObject.Parse(result);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Authorization", "Bearer " + jsonData["accessToken"]);
return RedirectToAction("PostLogin");
}
else
{
ModelState.AddModelError("", "These credentials does not work. Hmm..");
}
return View(loginModel);
}
Now in this if user successfully login then I am getting token and also I am setting that in header. Now after that I am redirecting user to PostLogin method.
But in that method I am not able to access that token. Below is code for that.
public async Task<ActionResult> PostLogin()
{
var accessToken = await HttpContext.GetTokenAsync("accessToken"); // Here I can't see token. and getting value as per below.
return View();
}
Even I am getting this error as per below :
InvalidOperationException: No authenticationScheme was specified, and there was no DefaultAuthenticateScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).
You have commented that at start of controller you have HttpClient client = new HttpClient(); which is new instance of HttpClient
So in your function you are setting that objects authorization using this line: client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Authorization", "Bearer " + jsonData["accessToken"]);.
After that you are getting users authorization from current HttpContext which is sent each time user visits some link and it does not have anything with your client object you just set authorization to.
Since you are redirecting you must change current HttpContext authorization header since it will be carried to redirected request. Do it by changing HttpContext.Request.Headers["Authorization"]. Problem is that it will only last that request so you need to give token back to client so he sends it in header each time he makes request.
I am attempting to wire up source control with continuous integration to an Azure Web App programmatically via C#. Historically I've used Azure PowerShell to do the same.
On the C# side, I am using the Microsoft.Azure.Management.Fluent libraries. The code to wire up source control is pretty straight forward:
await webApp.Update().DefineSourceControl().WithContinuouslyIntegratedGitHubRepository(GIT_URL).WithBranch(GIT_BRANCH).WithGitHubAccessToken(GIT_TOKEN).Attach().ApplyAsync();
This code runs for about 5 minutes and then returns the error:
{"Code":"BadRequest","Message":"Parameter x-ms-client-principal-name
is null or empty."}
I initially interpreted this to mean the Fluent libraries weren't passing a necessary value to the API, so I attempted to hit the API directly using Fluent libraries to abstract the authorization piece:
var credentials = SdkContext.AzureCredentialsFactory.FromServicePrincipal( CLIENT_ID, CLIENT_SECRET, TENANT_ID, AzureEnvironment.AzureGlobalCloud);
var client = RestClient.Configure().WithEnvironment(AzureEnvironment.AzureGlobalCloud).WithCredentials(credentials).Build();
CancellationToken cancellationToken = new CancellationToken();
var request = new HttpRequestMessage(HttpMethod.Get, $"https://management.azure.com/subscriptions/{SUBSCRIPTION_ID}/resourcegroups/{RESOURCE_GROUP}?api-version=2019-10-01");
request.Headers.Add("x-ms-client-principal-name", USERNAME);
client.Credentials.ProcessHttpRequestAsync(request, cancellationToken).GetAwaiter().GetResult();
var httpClient = new HttpClient();
var response = httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).GetAwaiter().GetResult();
var result = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
Several variations of this resulted in the same error referencing x-ms-client-principal-name. This lead me to believe the problem was with the token being used and the associated permissions. To test this, I ran the PowerShell script mentioned above, watched it run through Fiddler, and grabbed the token it used to complete. Using that token with basically the same code, it worked fine:
var token = "<THE TOKEN I COPIED FROM FIDDLER>";
CancellationToken cancellationToken = new CancellationToken();
var request = new HttpRequestMessage(requestType, $"https://management.azure.com/subscriptions/{SUBSCRIPTION_ID}/resourceGroups/{RESOURCE_GROUP}/providers/Microsoft.Web/sites/{WEB_APP}/sourcecontrols/web?api-version=2015-08-01");
request.Headers.Add("Authorization", $"Bearer {token}");
if ((requestType == HttpMethod.Put || requestType == HttpMethod.Post) && !string.IsNullOrEmpty(postData))
{
request.Content = new StringContent(postData, Encoding.UTF8, "application/json");
}
var httpClient = new HttpClient();
var response = httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).GetAwaiter().GetResult();
var result = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
So now, it seems to just be a matter of getting the access token myself. This is where things have become difficult. If I obtain the token as the service principal, I end up with the same x-ms-client-principal-name that I started with:
public static string GetAuthToken(string tenantId, string clientId, string clientSecret)
{
var request = new HttpRequestMessage(HttpMethod.Post, $"https://login.microsoftonline.com/{tenantId}/oauth2/token");
request.Content = new StringContent($"grant_type=client_credentials&client_id={clientId}&client_secret={clientSecret}&resource=https://management.azure.com", Encoding.UTF8, "application/x-www-form-urlencoded");
CancellationToken cancellationToken = new CancellationToken();
var httpClient = new HttpClient();
var response = httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).GetAwaiter().GetResult();
var result = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
var auth = JsonConvert.DeserializeObject<AuthResponse>(result);
return auth.AccessToken;
}
When I attempt to obtain token using my username and password, I get an error back telling me:
AADSTS90002: Tenant '' not found. This may happen if there are no
active subscriptions for the tenant. Check to make sure you have the
correct tenant ID. Check with your subscription administrator.
Here's the code I use for that:
public static string GetAuthToken(string tenantId, string username, string password, string clientId, string clientSecret)
{
var request = new HttpRequestMessage(HttpMethod.Post, $"https://login.microsoftonline.com/{tenantId}/oauth2/token");
request.Content = new StringContent($"grant_type=password&username={username}&password={password}&client_id={clientId}&client_secret={clientSecret}&resource=https://management.azure.com", Encoding.UTF8, "application/x-www-form-urlencoded");
CancellationToken cancellationToken = new CancellationToken();
var httpClient = new HttpClient();
var response = httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).GetAwaiter().GetResult();
var result = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
var auth = JsonConvert.DeserializeObject<AuthResponse>(result);
return auth.AccessToken;
}
I cannot have the C# code invoke the PS script as a work-around as it requires Azure PowerShell which is not guaranteed to be on the machine running the code (Azure Web App) and cannot be installed due to Admin restrictions.
I need to be able to obtain an access token that also has permissions for azure dev ops (formally VSTS) so I can bind/wire-up source control for continuous integration. Any guidance that can help me get past this is much appreciated.
I was finally able to get it working. First, I had to create a new Azure AD User and grant it the necessary permissions; this would not work with my regular Azure Login. With the new user and permissions, I am able to successfully obtain token like so:
public static async Task<string> GetAccessToken(string clientId, string userName, string password)
{
AuthenticationContext authenticationContext = new AuthenticationContext("https://login.microsoftonline.com/<TENANT_ID>");
var resourceId = "https://management.azure.com";
var result = await authenticationContext.AcquireTokenAsync(resourceId, clientId, new UserPasswordCredential(userName, password));
return result.AccessToken;
}
This token then works for wiring up source control and continuous integration.
I have a C# desktop that needs to use HttpClient to make a post request to my node API. The problem is that the JSON that the node app receives is different than what I am intending. I have tried using the example below and I have read from the StringContent and seen that it contains the values that I expect but it is showing up on the node app as {};
User user = new User
{
Username = username,
Password = password
};
StringContent content = new StringContent(JsonConvert.SerializeObject(user));
HttpResponseMessage response = await client.PostAsJsonAsync("auth", content);
I have also tried similar code but instead of StringContent I used only string like this:
User user = new User
{
Username = username,
Password = password
};
StringContent content = JsonConvert.SerializeObject(user);
HttpResponseMessage response = await client.PostAsJsonAsync("auth", content);
But this gives me an error about Unexpected Token " in JSON at position 0;
Please help me understand how I can send a properly serialized user object. I would prefer doing this without implementing ISerializable if possible. here is my User data class:
namespace Cloud_Calendar
{
class User
{
public string Username { get; set; }
public string Password { get; set; }
}
}
I think that it might be useful to see what my node is doing so here it is:
let failCount = 0;
app.all('/failAuth', (req, res) => {
console.log("failed" + (++failCount));
console.log(req.body);
res.send('failure to authenticate user');
});
app.post('/main', (req, res) => { //this will be removed & replaced with routes for each action
res.send('success');
});
app.post('/auth', passport.authenticate('local', { failureRedirect: '/failAuth' }), (req, res) => {
//TODO: if success parse req.body to search for req.body.action variable and redirect based on value
console.log(`req.body.action: ${req.body.action}`); //this indicates desired action
try{
res.redirect(308, '/main');
}catch(err){
console.error(err);
}
});
req.body is logged as empty... is this because of the redirect?
With PostAsJsonAsync you need to pass the object as is. Internally it will serialize the object, create a StringContent and post that via the HttpClient
So either use PostAsJsonAsync as it was intended
User user = new User
{
Username = username,
Password = password
};
HttpResponseMessage response = await client.PostAsJsonAsync("auth", user);
Or perform the serialization yourself (basically what PostAsJsonAsync does internally)
User user = new User
{
Username = username,
Password = password
};
string json = JsonConvert.SerializeObject(user);
StringContent content = new StringContent(josn, Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.PostAsync("auth", content);
I am trying to delete an Azure function from my Function App through C#.
But while deleting it programmatically, the function is not seen on the User Interface, but when I check it through Advanced tools (Kudu), I can still see my Azure function.
So basically while deleting the Azure function, what I do is, I delete it's function.json, and by doing so the Azure function isn't visible in Functions App list (see image below)
But when I go to Advanced Kudu to check whether it has been deleted, I can still see it, but without the function.json file. I had done this before (around 6 months back) and back then it was working properly. I don't know if I am doing it wrong or has anything changed.
Any help with the code would be appreciated.
Thanks
Edit:
The details that I have with me is the Function App's username, password, url, name (https://my-function-app.scm.azurewebsites.net/api/vfs/site/wwwroot), and azure function's name.
A little sample code of what I did which worked 6 months back
private WebClient _webClient = new WebClient
{
Headers = { ["ContentType"] = "application/json" },
Credentials = new NetworkCredential(username, password),
BaseAddress = functionsSiteRoot,
};
var functionJson =
JsonConvert.DeserializeObject<FunctionSettings>(_webClient.DownloadString("MyFunctionName/function.json"));
_webClient.Headers["If-Match"] = "*";
_webClient.UploadString("MyFunctionName/function.json", "DELETE", JsonConvert.SerializeObject(functionJson));
You could use REST API to perform this operation.
https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/functions/{functionName}?api-version=2016-08-01
Method: DELETE
Code Snippet:
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Delete, string.Format("https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/functions/{functionName}?api-version=2016-08-01", "Pass All Param In {}")));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", results.access_token);
HttpResponseMessage response = await _client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
dynamic objApiResponse = JsonConvert.DeserializeObject<dynamic>(await response.Content.ReadAsStringAsync());
}
else
{
return req.CreateResponse(HttpStatusCode.OK, "Sorry Invalid Request");
}
For details please have a look on official docs
Note: For token request your resource/Scope should be https://management.azure.com. Pass your token while send request.
Update:
You can request for token using client_credentials authentication flow. Try below format:
Azure Portal Credentials For App Id and Tenant Id:
Application Secret from Portal:
Token Endpoint Or URL:
https://login.microsoftonline.com/YourTenantName.onmicrosoft.com/oauth2/token
Request Param:
grant_type:client_credentials
client_id:b603c7be_Your_App_ID_e6921e61f925
client_secret:Vxf1Sl_Your_App_Secret_2XDSeZ8wL/Yp8ns4sc=
resource:https://graph.microsoft.com
PostMan Sample:
Token On Response:
Code Snippet For Token:
//Token Request End Point
string tokenUrl = $"https://login.microsoftonline.com/YourTenant/oauth2/token";
var tokenRequest = new HttpRequestMessage(HttpMethod.Post, tokenUrl);
//I am Using client_credentials as It is mostly recomended
tokenRequest.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = "20e08e95-_Your_App_ID_e9c711b0d19e",
["client_secret"] = "+trl[ZFl7l_Your_App_Secret__ghon9",
["resource"] = "https://management.azure.com/"
});
dynamic json;
AccessTokenClass results = new AccessTokenClass();
HttpClient client = new HttpClient();
var tokenResponse = await client.SendAsync(tokenRequest);
json = await tokenResponse.Content.ReadAsStringAsync();
results = JsonConvert.DeserializeObject<AccessTokenClass>(json);
//New Block For Accessing Data from API
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Delete, string.Format("https://management.azure.com/subscriptions/YOurSubscription/resourceGroups/YourResourceGroup/providers/Microsoft.Web/sites/DeleteTestFuncAppName/functions/DeleteFunctionNameThatYouWantToDelete?api-version=2016-08-01"));
//Passing Token For this Request
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", results.access_token);
HttpResponseMessage response = await client.SendAsync(request);
//Read Server Response
dynamic objServerResponse = JsonConvert.DeserializeObject<dynamic>(await response.Content.ReadAsStringAsync());
Class I Have Used:
public class AccessTokenClass
{
public string token_type { get; set; }
public string expires_in { get; set; }
public string resource { get; set; }
public string scope { get; set; }
public string access_token { get; set; }
public string refresh_token { get; set; }
}
Point To Remember:
If you got this error
InvalidAuthenticationToken: The received access token is not valid: at
least one of the claims 'puid' or 'altsecid' or 'oid' should be
present. If you are accessing as application please make sure service
principal is properly created in the tenant
You have to assign role to your application like below:
I am trying to make a httprequest to a web that is authenticated with ADFS of a private company.
I am able to login and get the token of my App. I am sure I am doing it correct due to I can get the contacts of my O365.After getting the token I try to make a request to the web. As I already have the token, I try to include it in the header of the request. The answer that I receive from the web is always the html with the login web not the result that I am requesting. As additional information I have added a "Connected Service" O365 API from VisualStudio.
This is my code:
public static async Task<string> GetAnswer(string wwweb)
{
var token = await GetAccessToken();
using (var client = new HttpClient())
{
var url = wwweb;
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
//client.DefaultRequestHeaders.ProxyAuthorization= new AuthenticationHeaderValue("Bearer", token);
// client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var response = await client.GetStringAsync(url);
return response;
}
}
I have tried with the 3 lines ( 2 commented and one not) without success, always giving back company's login web. Am I doing something wrong?
Thanks