403 Forbidden Error in Keycloak API (view-users) - c#

I'm having issues trying to access auth/admin/realms/{realm-name}/users API. I've already tried everything from other questions and answers, but nothing seems to work. Steps that I did:
The user already has a role that has realm-management and view-users on it.
I've already assigned this same role to my client in the scopes section.
I can't have Service Accounts Enabled in my client because I need to have Access Type as confidential, and that won't allow my user to access Login page from Application.
I've also already tried to give 'view-users' role to my user and to my client individually as a Client role, it also didn't work.
My code in C#:
var authState = await AuthenticationProvider.GetAuthenticationStateAsync();
var user = authState.user;
var tokenResult = await TokenProvider.RequestAccessToken();
_accessToken = _tokenResult.Value;
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken);
var request = new HttpRequestMessage(HttpMethod.Get, $"{url}/auth/admin/realms/{realm name}/users");
request.Headers.Add("Accept", "application/json");
var response = await _client.SendAsync(request);
// Rest of code that gets response and deserializes to array of users.
I honestly don't know what to do. Any help would be very appreciated. Thanks so much!

Since Keycloak's Rest API endpoints changes very often (e.g. BasePath, Signature etc.), I found very useful:
login to keycloak's admin panel (UI) and see from developer tools the endpoint for fetching the users (on Users menu item in Manage group parent). In particular, {$HOST}/admin/realms/{$REALM_NAME}/users is the endpoint in version 17.0.1.
I also assigned the client role view-users from realm-management to one of my realm users as it is displayed below (UI of version 17.0.1)
I was able to fetch the users by just adding user's access token to the request.

Related

How do I generate an assertion token for Sharepoint Access on behalf of another user in C#

I have a web application with a number of modules. One of the modules grabs a number of excel files from SharePoint directories, and then combines the data in them. So far, I have been just mapping the folders to OneDrive and accessing them that way. But this always uses my OneDrive credentials, which need to be refreshed from time to time. The right way to do this is to access them directly from Sharepoint on behalf of the user logged into my web application. I have the delegated API permission things set up in Azure, and I have the client ID and secret, etc.. I've been reading a number of articles on how to do this. All of them talk about how to get the token on behalf of someone else. These articles also talk about the assertion token needing to be passed in order to get the on behalf of token. However, they don't tell you how to get the assertion token in the first place. Here is the code I currently have:
'''var client = new RestClient("https://login.microsoftonline.com/XXXX/oauth2/v2.0/token");
var request = new RestRequest();
request.Method = Method.Post;
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
request.AddParameter("client_id", "MYCLIENTID", ParameterType.GetOrPost);
request.AddParameter("client_secret", "MYSECRET", ParameterType.GetOrPost);
request.AddParameter("scope", "https://MYTenent.sharepoint.com/.default", ParameterType.GetOrPost);
request.AddParameter("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer", ParameterType.GetOrPost);
request.AddParameter("requested_token_use", "on_behalf_of", ParameterType.GetOrPost);
RestResponse response = client.Execute(request);'''
The result of this is of course an error that the assertion was not supplied. I didn't supply any more code, because I can't even get passed this. The rest of my code takes the token and passes it to an auth provider, which is then used to instantiate the GraphServiceClient. Based on what I've read, that client is then used to get the lists, files, etc...
So, my question is, how do I get the assertion token in the first place? I'm hoping the code I have written so far is in the correct direction and all I'm missing is the assertion token.
UPDATE:
I've gotten one answer that really didn't help. I pretty much copied and pasted the code (replacing the clientID, etc..) and I received an error> I was going to copy and paste it from the solution comments provided in the answer, but I guess you can't do that while editing.
Someone also asked if I was able to get the auth code from the URL. The answer to that is no. We use 2 factor authentication, and I tried to manually look at the URLS as I was logging in, while using break points to slow things down a bit. And I did not see an auth code. I did put a break point directly after the line of code:
var info = await _signInManager.GetExternalLoginInfoAsync();
And when I look at the info variable, I can see 4 tokens. One of them is an access token and another is an ID token. The last one is an expiration date. I don't see an auth code, and from what I understand, by the time I see the access code, it's too late. The auth code was already used to get the access code.
UPDATE 2:
I know that OBO is not what I want. I also know that in order to use delegated permissions, I need to use the Auth Code flow and not client credentials. I can't seem to get the auth code from the users initial log in. And I don't know how to get it otherwise.
For those of you that might be thinking "Does he need to be spoon fed?", the answer is yes, I do. I need a simple code example that will get me the auth code, so I can use it in the rest of the code I already have. If anyone can paste that code into an answer and not provide a link, that would be great. I'm sorry, but the links I have been given, just go to microsoft learn sites that go through the explaination, but don't give any code samples.
The OBO flow is obviously not applicable in your context, and if you're going to get an access token on behalf of a logged in user, then you should focus on auth code flow or ROPC flow.
The corresponding C# code segment is:
using Microsoft.Graph;
using Azure.Identity;
var scopes = new[] { "https://{tenant-name}.sharepoint.com/.default" };
// Multi-tenant apps can use "common",
// single-tenant apps must use the tenant ID from the Azure portal
var tenantId = "tenant id";
// Values from app registration
var clientId = "client id";
var clientSecret = "client secret";
// For authorization code flow, the user signs into the Microsoft
// identity platform, and the browser is redirected back to your app
// with an authorization code in the query parameters
var authorizationCode = "authorization code ";
// using Azure.Identity;
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
// https://learn.microsoft.com/dotnet/api/azure.identity.authorizationcodecredential
var authCodeCredential = new AuthorizationCodeCredential(
tenantId, clientId, clientSecret, authorizationCode, options);
var accessToken = await authCodeCredential.GetTokenAsync(new Azure.Core.TokenRequestContext(scopes) { });
Console.WriteLine(accessToken.Token);
//var graphClient = new GraphServiceClient(authCodeCredential, scopes);

How to fix issue calling Amazon SP-API, which always returns Unauthorized, even with valid Token and Signature

I went through the guide of for getting setup to call the new SP-API (https://github.com/amzn/selling-partner-api-docs/blob/main/guides/developer-guide/SellingPartnerApiDeveloperGuide.md), and during the process checked off all of the api areas to grant access to (i.e. Orders, Inventory, etc). I am using the C# library provided by Amazon (https://github.com/amzn/selling-partner-api-models/tree/main/clients/sellingpartner-api-aa-csharp). I successfully get an access token and successfully sign the request, but always get the following error:
Access to requested resource is denied. / Unauthorized, with no details.
I am trying to perform a simple get to the /orders/v0/orders endpoint. What am I doing wrong?
Below is my code:
private const string MARKETPLACE_ID = "ATVPDKIKX0DER";
var resource = $"/orders/v0/orders";
var client = new RestClient("https://sellingpartnerapi-na.amazon.com");
IRestRequest restRequest = new RestRequest(resource, Method.GET);
restRequest.AddParameter("MarketPlaceIds", MARKETPLACE_ID, ParameterType.QueryString);
restRequest.AddParameter("CreatedAfter", DateTime.UtcNow.AddDays(-5), ParameterType.QueryString);
var lwaAuthorizationCredentials = new LWAAuthorizationCredentials
{
ClientId = AMAZON_LWA_CLIENT_ID,
ClientSecret = AMAZON_LWA_CLIENT_SECRET,
RefreshToken = AMAZON_LWA_REFRESH_TOKEN,
Endpoint = new Uri("https://api.amazon.com/auth/o2/token")
};
restRequest = new LWAAuthorizationSigner(lwaAuthorizationCredentials).Sign(restRequest);
var awsAuthenticationCredentials = new AWSAuthenticationCredentials
{
AccessKeyId = AMAZON_ACCESS_KEY_ID,
SecretKey = AMAZON_ACCESS_SECRET,
Region = "us-east-1"
};
restRequest = new AWSSigV4Signer(awsAuthenticationCredentials).Sign(restRequest, client.BaseUrl.Host);
var response = client.Execute(restRequest);
If you followed the SP-API guide, then you created a Role (which is the IAM ARN your app is registered with) and a User which has permissions to assume that role to make API calls.
However, one thing the guide is not clear about is that you can't make API calls using that user's credentials directly. You must first call the STS API's AssumeRole method with your User's credentials (AMAZON_ACCESS_KEY_ID/AMAZON_ACCESS_SECRET), and it will return temporary credentials authorized against the Role. You use those temporary credentials when signing requests.
AssumeRole will also return a session token which you must include with your API calls in a header called X-Amz-Security-Token. For a brief description of X-Amz-Security-Token see https://docs.aws.amazon.com/STS/latest/APIReference/CommonParameters.html
You also get this error if your sp app is under review, drove me nuts!
If you using c# take look to
https://github.com/abuzuhri/Amazon-SP-API-CSharp
AmazonConnection amazonConnection = new AmazonConnection(new AmazonCredential()
{
AccessKey = "AKIAXXXXXXXXXXXXXXX",
SecretKey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
RoleArn = "arn:aws:iam::XXXXXXXXXXXXX:role/XXXXXXXXXXXX",
ClientId = "amzn1.application-XXX-client.XXXXXXXXXXXXXXXXXXXXXXXXXXXX",
ClientSecret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
RefreshToken= "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
});
var orders= amazonConnection.Orders.ListOrders();
In our situation, we had to explicitly add an IAM policy to the user we defined as making the API call. Please see the link below and confirm that the user you have calling the API has the policy assigned to them:
https://github.com/amzn/selling-partner-api-docs/blob/main/guides/developer-guide/SellingPartnerApiDeveloperGuide.md#step-3-create-an-iam-policy
Somehow we went through the step-by-step setup twice, and adding this explicit policy was missed. Initially I believe it was added 'inline' as instructed, but that does not seem to work.
I dont think is a duplicated question, buy the solution may apply: https://stackoverflow.com/a/66860192/1034622

Azure B2C Custom Extension Attribute Not Updating

I am trying to add/update a value to a custom extension attribute for a user in Azure AD B2C. The attribute is an AccountNumber with the type as string. I have two different Azure environments I'm working out of. One is for my local/staging environments, and the other is client's production Azure environment. My local and staging are working fine, but I cannot seem to get this attribute to get updated through the production instance, which is leading me to think I'm missing some sort of permission/configuration within the Azure instance itself rather than code, but let's see.
Here are the steps I've taken in Azure:
Within the B2C I've setup my application. For the API Access section I have 2 selections
Acquire an id_token for users (openid)
Acquire a refresh_token for users (offline_access)
I have two user flows both of which are returning AccountNumber as a claim.
Within App registrations (NOT legacy), I've added my application as well. It does have a warning about not being supported yet in B2C, but I have this in my staging instance as well. I have the following for API Permissions that have been selected for this application. I've gone back and forth adding the offline_access, openid, and profile. (All are uder Microsoft Graph Delegated)
User.Read
User.ReadWrite
offline_access
openid
profile
In App registrations/Authentication tab I've enabled implicit grant flow and checked both Access tokens and ID tokens
Ive taken the b2c-extensions-app app ID and saved it within my code for using to update the extensions attribute (dashes removed)
Web.config
<add key="ida:NonAdminScopes" value="User.Read User.ReadWrite" />
<add key="ida:AdminScopes" value="Directory.AccessAsUser.All User.ReadWrite.All Group.ReadWrite.All" />
Here is where I'm building and making the request. Yes, I realize I'm doing this a more manual way, but I'm also working with Sitecore which has required me to keep some older dlls and this is where I've landed after days of frustration.
private async Task<string> SendGraphPatchRequest(string json, string objectId)
{
var graphEndpoint = new Uri(string.Format("https://graph.microsoft.com/v1.0/{0}/users/{1}", Tenant, objectId));
HttpResponseMessage response;
using (var httpClient = new HttpClient { BaseAddress = graphEndpoint })
{
using (var requestMessage = new HttpRequestMessage(new HttpMethod("PATCH"), graphEndpoint))
{
var token = await GetOrCreateAccessToken();
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
if (!string.IsNullOrEmpty(json))
{
requestMessage.Content = new StringContent(json, Encoding.UTF8, "application/json");
}
response = await httpClient.SendAsync(requestMessage).ConfigureAwait(continueOnCapturedContext: false);
if (!response.IsSuccessStatusCode)
{
string error = await response.Content.ReadAsStringAsync();
Logger.Error(string.Format("Error -> RequestMessage: {0}", error));
object formatted = JsonConvert.DeserializeObject(error);
throw new WebException("Error Calling the Graph API: \n" + JsonConvert.SerializeObject(formatted, Formatting.Indented));
}
}
}
return response.Content.ReadAsStringAsync().Result;
}
The line that is creating the access token does use ClientCredentials
_accessToken = await _authContext.AcquireTokenAsync("https://graph.microsoft.com/", _credentials);
An example of the request body content
{"extension_[extensionAppId]_AccountNumber":"123456"}
And when I try to make this request with Postman (I had a line of code where I was logging the token that gets created from the above code snippet - not sure if that will actually work or not), here is the response I get back:
{
"error": {
"code": "Authorization_RequestDenied",
"message": "Insufficient privileges to complete the operation.",
"innerError": {
"request-id": "####",
"date": "2019-08-21T15:06:45"
}
}
}
Incorrect permissions on the App Reg for MS Graph API.
Probably you have consented to other permissions in your dev tenants then deselected them later, it doesn’t remove consent.
Add read/write directory under Application permissions since you use client credentials in code,delegated permissions won’t do anything here. Also click Grant Permissions once you save the new permissions.

Retrieving user information in Azure Function (C#) from Angular-cli application

I'm having troubles retrieving user information inside an Azure Function and have no idea how to do this. I've tried different things already, but nothing seems to work...
First of all, I created an Angular-cli application and am able to login using the "adal-angular5" npm-package.
When I want to retrieve information from a HttpTriggering Azure function, I can't seem to find how to get more information about the user using the token from the angular app (or how to validate the logged in user). I'm including it in the headers of the message.
headers.append('Authorization', `Bearer ${this.adal5Service.userInfo.token}`);
I've created an app registration in Azure AD including my reply URL, Permissions to "Microsoft Graph" and "Windows Azure Active Directory"
Does anyone know how to do this? (If more information should be necessary to solve... just tell me, and I'll happily provide that)
Things I tried already
Code below with both requestUrl's (one is in comment) and with/without sending the token in the header
using (var client = new HttpClient())
{
//var requestUrl = $"https://graph.windows.net/me?api-version=1.6";
var requestUrl = $"https://graph.microsoft.com/v1.0/me/";
var request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = client.SendAsync(request).Result;
var responseString = response.Content.ReadAsStringAsync().Result;
var user = JsonConvert.DeserializeObject<AdUser>(responseString);
return user;
}

Azure AD authentication with asp.net Identity for authorisation

I tried to look for all over internet but couldn't see how I can achieve what I was asked to. Here is my enterprise app which uses Asp.net Identity for form based authentication. I had extended User and Role along with Groups to provide authorization in my code. (note: not using any group/role directives).
Now I was asked to look at possibility of changing code to accommodate Azure Active Directory authentication. I tried reading on how you can register app, send user to Azure site for authentication, get back token etc. However I'm stuck at 'what-afterwards?' I have authenticated user How can I use my existing Asp.net Identity model where user was stored in sql database. How to use this token to relate the existing user.
Moreover, when I change my project to allow Azure AD, it removes Aspnet.Identity package as its not compatible with Azure AD !!
I even tried manually keeping both packages side by side, I got to point where user is sent to authenticate on Azure, diverted back to home page and again to login on Azure AD in never ending loop.
to summarize the question, How can I authenticate user from AAD and keep using existing Roles and groups authorization.
Edit:
I tried creating separate web service which will authenticate user and send JWT token. which works find if I call it directly on browser, however, when I tried to call this service from my web app I get weird error
Application with identifier 'a2d2---------------' was not found in the directory azurewebsites.net
Weird part here is name of directory is 'azurewebsites.net' and not the default directory I'm using.
Update
Here is code which throws error
public async Task<ActionResult> Login(string returnUrl)
{
try
{
// get the access token
AuthenticationContext authContext = new AuthenticationContext(authority, new TokenCache());
var clientCredential = new ClientCredential(clientId, password);
//Error on below line
AuthenticationResult result = await authContext.AcquireTokenAsync(resourceId, clientCredential);
// give it to the server to get a JWT
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
......
try this:
var client = new RestClient("https://login.microsoftonline.com/{tenant-
Id}/oauth2/v2.0/token");
var request = new RestRequest(Method.POST);
request.AddHeader("cache-control", "no-cache");
request.AddHeader("content-type", "application/x-www-form-urlencoded");
request.AddHeader("grant_type", "password");
request.AddParameter("application/x-www-form-urlencoded",
"grant_type=password&client_id={client-Id}&client_secret={client-
secret}&scope={scopeurl}&userName={username}&password={password}",
ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
var json = response.Content;
var JSONObject = JObject.Parse(json);
var token = (string)JSONObject["access_token"];
I had a similar issue so I created an Office 365 owin security plugin. I shared the code on github. It's based on the katana project at codeplex.
You can find the source code at https://github.com/chadwjames/wakizashi.
You will need to register your application here. When registering the application set the call back uri to https://yourdomain/signin-office365
The Application ID is your Client Id and the Password is your Client Secret.
Once you have it registered you can modify the Startup.Auth.cs and add something like this to the ConfigureAuth method.
//setup office 365
var office365Options = new Office365AuthenticationOptions
{
ClientId = ConfigurationManager.AppSettings["ada:ClientId"],
ClientSecret = ConfigurationManager.AppSettings["ada:ClientSecret"],
Provider = new Office365AuthenticationProvider
{
OnAuthenticated = async context =>
{
await
Task.Run(
() => context.Identity.AddClaim(new Claim("Office365AccessToken", context.AccessToken)));
}
},
SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie
};
office365Options.Scope.Add("offline_access");
app.UseOffice365Authentication(office365Options);
When I have more time I hope to create a nuget package for this.

Categories