I'm testing out IdentityServer4, going through the documentation in order to learn more about OAuth2, OpenId Connect and Claim-based authentication, all of which I'm new at. However, some of the example code behaves weirdly and I can't figure out why...
So from my understanding, when given permission to access user data, the client can reach out to the UserInfo endpoint, which contains data such as claims, etc.
In IdentityServer4 there's even a setting
GetClaimsFromUserInfoEndpoint
that the documentation recommends we set to true.
So I'm following the IdentityServer4 startup guides and everything works perfectly until a point. This Quickstart contains the example code provided, although I'm assuming that I'm missing something obvious and seeing the code is not required.
Based on the openId Configuration page of the running server, the userinfo endpoint is located at
http://localhost:5000/connect/userinfo and when I try to access it via the browser I'm seeing a navbar which claims I'm logged in, but the body of the page is a signin prompt. Looks weird but I'm assuming that this is because I'm logged in at localhost:5000 (IdentityServer4), but I'm not sending the userId token which I got for the client on localhost:5002.
So I wrote the following code on my client app:
public async Task<IActionResult> GetData()
{
var accessToken = HttpContext.Authentication.GetTokenAsync("access_token").Result;
HttpClient client = new HttpClient();
client.SetBearerToken(accessToken);
var userInfo = await client.GetStringAsync("http://localhost:5000/connect/userinfo");
return Content(userInfo);
}
Here I know that GetTokenAsync("access_token") should work as it's used in other places in the example project by the client app that connect to an API. However, the responce I'm getting is again the layout page of IdentityServer and a log in prompt.
Any idea what my mistake is and how to access the UserInfo endpoint?
Edit: removed thread-blocking so that I don't show strangers shameful test code
Ok, so it turns out that this code should have a simplified version, namely:
UserInfoClient uic = new UserInfoClient("http://localhost:5000", idToken);
var result = await uic.GetAsync();
return Content(JsonConvert.SerializeObject(result.Claims));
Yet, the problem persists, even the encapsulated code inside UserInfoClient hits the brick wall of "no user endpoint data, just the layout for the example website".
It's probably little late to answer, but for anyone who is still stumbling upon this, try this ---
var accessToken = await HttpContext.Authentication.GetTokenAsync("access_token");
var client = new HttpClient();
client.SetBearerToken(accessToken);
var userInfoClient = new UserInfoClient("http://localhost:5000/connect/userinfo");
var response = await userInfoClient.GetAsync(accessToken);
var claims = response.Claims;
You can also get the list of claims on the client app like -
var claims = HttpContext.User.Claims.ToList();
without calling the endpoint.
Related
I'm running into issues when trying to create an event in a specific user calendar.
This call works fine: POST https://graph.microsoft.com/v1.0/me/events
But when I change the API Call to include the other user details, it throws this error: "The specified object was not found in the store."
I have created an app on Azure and assigned all necessary permissions.
App Permissions
Error:
Can someone please assist if I'm missing something?
Please note when you use /me, it means you are calling the ms graph api with a delegate api permission which is authentiated by entering user name/password, you can only do operations on your own account with this kind of authentication. While you want to do operations for other users like /users/user_id/xxx, you required the application api permission. That's why api document showed api permission in Delegated and Application. One for personal and another for all users.
When we need to get access token contain application permission, we need to use client credential flow. This flow is used for daemon application since this kind of application doesn't have user interactive operation, so we can only use application permission for this kind of scenario. And as you can see it will offer "very big ability" to the application(allow application to create/change/delete items for any user in your tenant), so we need to use appliation permission with caution.
Come back to the case, you can follow this section to generate access token and call the api. You can also using graph SDK in your code to call that api.
using Azure.Identity;
using Microsoft.Graph;
public async Task<string> testAsync() {
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "tenant_name.onmicrosoft.com";
var clientId = "azure_ad_clientid";
var clientSecret = "client_secret";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
var a = await graphClient.Users["user_id"].Request().GetAsync();
return a.DisplayName;
}
I am having trouble getting my current user's access_token.
Here is my setup:
QuickstartIdentityServer (QIS) in aspnet core, identity and EF storage
API (API) in NodeJs. Validates jwt tokens in header against QIS.
SPA angular app that works great with QIS and API and is out of the scope of this question
In a section of the QuickstartIdentityServer (QIS) site (user details page), I would like to call an API endpoint using an access_token to authenticate the request. I am struggling to retrieve the current user's access_token from my QIS site. Whenever I call HttpContext.GetTokenAsync("access_token") I get a null value. I have seen this section of IdSrv4 documentation: https://identityserver4.readthedocs.io/en/release/quickstarts/5_hybrid_and_api_access.html?highlight=gettokenasync but it seems to apply to an MVC client and not my own identity server.
Anyone could shed some light on how to get my user's access_token ?
Thanks
EDIT
Here is a starting point to try to explain better my issue:
https://github.com/IdentityServer/IdentityServer4.Samples/tree/release/Quickstarts/6_AspNetIdentity/src/IdentityServerWithAspNetIdentity
Starting from this QIS project, I would like to get the logged in user's access token. So for instance, if I edit HomeController to add this call:
public async Task<IActionResult> Index()
{
var accessToken = await HttpContext.GetTokenAsync("access_token");
return View(accessToken);
}
I would then be able to call my NodeJS API with this token in the Auth Header.
Hope this explains better my issue.
So I managed to authenticate myself w/ my API using a dedicated Client using client credentials grant and the following call to get an access_token:
var disco = await DiscoveryClient.GetAsync("http://localhost:5000");
var tokenClient = new TokenClient(disco.TokenEndpoint, clientId, clientSecret);
var tokenResponse = await tokenClient.RequestClientCredentialsAsync(scope);
Then I can add to my request header to API the access_token returned in tokenResponse:
using(var client = new HttpClient()) {
client.SetBearerToken(tokenResponse.AccessToken);
...
// execute request
}
The downside is that I can't "impersonate" the current currently logged on IS on API side.
Can't believe I'm stuck with a LOGIN :( hate when this happens.
Can somebody enlight me how to connect TF.EXE by using PAT password or in the best case an OAuth token?
I might add that I already have a Pat token and an OAuth token, not a problem while trying to get those, but every time I try this example:
TF.exe workspaces /collection:xxxx.visualstudio.com/xxxx /loginType:OAuth /login:.,MyPatTokenOrMyOauthToken /noprompt
I get the following response:
TF30063: You are not authorized to access xxxx.visualstudio.com\xxxx.
So, I Know command it's ok, because if I don't specify a login, a modal window prompts for credentials, and I tested already with that approach and works fine.
For the end, I might change everything to change tf.exe for the TFS api, but I'm unable to find same methods in the api (see reference: https://learn.microsoft.com/es-es/rest/api/vsts/?view=vsts )
If API has same methods than TF.exe, that will be useful, but so far I don't see same methods in the API.
Hope somebody has the solution for my problem.
Thanks in advance.
From my test, PAT token doesn't work in the following command, you have to get a OAuth token:
tf workspaces /collection:https://xxxx.visualstudio.com /loginType:OAuth /login:.,[OAuth token]
For the api that authenticate with Visual Studio Team Services (VSTS), you could refer to the examples in this link:
Here is an example getting a list of projects for your account:
REST API
using System.Net.Http;
using System.Net.Http.Headers;
...
//encode your personal access token
string credentials = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", "", personalAccessToken)));
ListofProjectsResponse.Projects viewModel = null;
//use the httpclient
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("https://{accountname}.visualstudio.com"); //url of our account
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials);
//connect to the REST endpoint
HttpResponseMessage response = client.GetAsync("_apis/projects?stateFilter=All&api-version=1.0").Result;
//check to see if we have a succesfull respond
if (response.IsSuccessStatusCode)
{
//set the viewmodel from the content in the response
viewModel = response.Content.ReadAsAsync<ListofProjectsResponse.Projects>().Result;
//var value = response.Content.ReadAsStringAsync().Result;
}
}
.Net Client Libraries
using Microsoft.TeamFoundation.Core.WebApi;
using Microsoft.VisualStudio.Services.Common;
...
//create uri and VssBasicCredential variables
Uri uri = new Uri(url);
VssBasicCredential credentials = new VssBasicCredential("", personalAccessToken);
using (ProjectHttpClient projectHttpClient = new ProjectHttpClient(uri, credentials))
{
IEnumerable<TeamProjectReference> projects = projectHttpClient.GetProjects().Result;
}
Add a screenshot:
Update:
I've tested with a new account, and the result is as below. If I remove /loginType and /login parameters, a window will pop up to ask me logon.
The screenshot without /loginType and /login parameters:
The screenshot with /loginType and /login parameters:
I have a WebApi app that is using Windows Azure Active Directory Bearer Authentication to authenticate users. After the user is authenticated, I want to query Azure's Graph Api to get more information about the user.
I have a solution that works, but seems very hacky. I read the Authorization header and strip out the bearer part, and then I use AquireToken to get the new token:
var authHeader = HttpContext.Current.Request.Headers["Authorization"];
var tokenMatch = Regex.Match(authHeader, #"(?<=^\s*bearer\s+).+$", RegexOptions.IgnoreCase);
var result = authInfo.AuthContext.AcquireToken(resourceId, authInfo.Credential,
new UserAssertion(tokenMatch.Value));
return result.AccessToken;
There has to be a better way, but I've tried AcquireToken many different overloads and this was the only way I could get it to work. I tried AcquireTokenSilent, which works in my client app because there is a token in the TokenCache, but when I try in the WebApi, there doesn't seem anywhere to implement a TokenCache.
That is indeed somewhat hacky :-) see https://github.com/AzureADSamples/WebAPI-OnBehalfOf-DotNet for a way in which you can retrieve the incoming token through the ClaimsPrincipal. It boils down to passing TokenValidationParameters = new TokenValidationParameters{ SaveSigninToken = true } in the options and retrieving in from your controller or filter code via
var bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext as System.IdentityModel.Tokens.BootstrapContext;
I'm trying to access the Google Directory using a Service Account. I've fiddled with the DriveService example to get this code:
public static void Main(string[] args)
{
var service = BuildDirectoryService();
var results = service.Orgunits.List(customerID).Execute();
Console.WriteLine("OrgUnits");
foreach (var orgUnit in results.OrganizationUnits)
{
Console.WriteLine(orgUnit.Name);
}
Console.ReadKey();
}
static DirectoryService BuildDirectoryService()
{
X509Certificate2 certificate = new X509Certificate2(SERVICE_ACCOUNT_PKCS12_FILE_PATH, "notasecret",
X509KeyStorageFlags.Exportable);
var provider = new AssertionFlowClient(GoogleAuthenticationServer.Description, certificate)
{
ServiceAccountId = SERVICE_ACCOUNT_EMAIL,
Scope = DirectoryService.Scopes.AdminDirectoryOrgunit.GetStringValue()
};
var auth = new OAuth2Authenticator<AssertionFlowClient>(provider, AssertionFlowClient.GetState);
return new DirectoryService(new BaseClientService.Initializer()
{
Authenticator = auth,
ApplicationName = "TestProject1",
});
}
When I run it, I get
ArgumentException: Precondition failed.: !string.IsNullOrEmpty(authorization.RefreshToken)
I'm going round in circles in the Google documentation. The only stuff I can find about RefreshTokens seems to be for when an individual is authorizing the app and the app may need to work offline. Can anyone help out or point me in the direction of the documentation that will, please.
Service Account authorization actually do not return Refresh Token - so this error makes sense. Do you know where this is coming from?
I am not too familiar with the .NET client library but having the full error trace would help.
As a longshot - The error might be a bad error -
Can you confirm that you've enabled the Admin SDK in the APIs console for this project
Can you confirm that you whitelisted that Client ID for the service account in the domain you are testing with (along with the Admin SDK scopes)
The above code will work if you replace the provider block with:
var provider = new AssertionFlowClient(GoogleAuthenticationServer.Description, certificate)
{
ServiceAccountId = SERVICE_ACCOUNT_EMAIL,
Scope = DirectoryService.Scopes.AdminDirectoryOrgunit.GetStringValue(),
ServiceAccountUser = SERVICE_ACCOUNT_USER //"my.admin.account#my.domain.com"
};
I had seen this in another post and tried it with my standard user account and it didn't work. Then I read something that suggested everything had to be done with an admin account. So, I created a whole new project, using my admin account, including creating a new service account, and authorising it. When I tried it, it worked. So, then I put the old service account details back in but left the admin account in. That worked, too.