Insufficient Permissions with Access Token to Azure Data Catalog - authentication

I'm trying to issue an API call to Azure Data Catalog using a Client Secret, however I get a Permission denied error.
I'm able to create the token and I've given the client application the necessary permissions in AAD.
If I change the authentication method to use delegated access where the user signs in, it works but if i try to use a client secret the generated token does not have the proper permissions.
Here's some code that I've altered using various samples
The token from this code works
//get the token
authResult = await authContext.AcquireTokenAsync(resourceUri, clientId, new Uri(redirectUri), new PlatformParameters(PromptBehavior.Always));
//the token from this code doesn't work
IConfidentialClientApplication app;
app = ConfidentialClientApplicationBuilder.Create(clientId)
.WithClientSecret(clientSecret)
.WithAuthority(new Uri(authorityuri))
.Build();
string[] scopes = new string[] { "https://api.azuredatacatalog.com/.default" };
result = await app.AcquireTokenForClient(scopes);
I get this error The remote server returned an error:
(403) AccessDenied : Access denied..

Following is high level steps to configure the Service Principal configuration to support ADC REST API
Azure Active Directory | App Registration – Select Web app / API for Application Type and URL can be anything Example:  http://portal.azure.com
Select the Application | Required Permission | Add the Microsoft Azure Data Catalog
Navigate to http://www.azuredatacatalog.com | Settings - Add the Service Principal to Catalog User based on the business need you can add to glossary admin / catalog admin. The format is clientid#tenantid
****Clientid = Azure Active Directory | App Registration | Application ID**
**TenantID = Azure Active Directory | Properties | Directory ID****
Follow the Service Principal Authentication sample REST API code to build your solution https://github.com/Azure-Samples/data-catalog-dotnet-service-principal-get-started

Related

Not able to access Bigquery dataset from .net app

I am new to GCP and have created a Project in GCP which has a service account with a principal associated with it.The principal has role of editor , owner and viewer. I have created a dataset which contains a table which i am not able to access from .net application.
Steps used to connect to read data from table :
1.Created service account to authenticate Api requests
2.Created key,json file which will be used by .net application to connect to the datasets(gcloud iam service-accounts keys create ~/key.json)
3.
using Google.Apis.Auth.OAuth2;
using Google.Cloud.BigQuery.V2;
Console.WriteLine("Hello, World!");
string dir = Directory.GetParent(Directory.GetCurrentDirectory()).Parent.Parent.FullName + "\\"+"currency-342912-7c734b3fad06.json";
GoogleCredential credential = GoogleCredential.FromFile(dir);
var client = BigQueryClient.Create("currency-342912", credential);
var table = client.GetTable("bigquery-public-data", "austin_311", "311_service_requests");
var sql = $"SELECT * FROM {table} LIMIT 10";
var results = client.ExecuteQuery(sql,parameters:null);
getting the below error :
Google.GoogleApiException: 'Google.Apis.Requests.RequestError
Access Denied: Project bigquery-public-data: User does not have bigquery.jobs.create permission in project bigquery-public-data
But user has browser, editor , owner , viewer all the permissions
As per the error in the question above -"Access Denied: Project bigquery-public-data: User does not have bigquery.jobs.create permission in project bigquery-public-data"
it is mentioned to have the role "bigquery.jobs.create" so i applied this role at the project level and the only area left was the service account , i applied this role to the service account and this started working

Azure SQL authenticate as user via API with MS Identity Platform

I have an ASP.NET Core 3.1 Web App calling an ASP.NET Core 3.1 Web API, which in turn accesses an Azure SQL database. Authentication is provided via MSAL (Microsoft Identity Platform) - i.e. using the relatively new Microsoft.Identity.Web and Microsoft.Identity.Web.UI libraries.
The goal is to ensure that the user pulls data from SQL via the API under the context of his/her own login, thus enabling row-level security, access auditing and other good things.
I have succeeded in getting the sign-in process to work for the Web App - and through that it obtains a valid access token to access the API using a scope I created when registering the latter with AD.
When I run both the API and the App locally from Visual Studio everything works as expected - the correct access tokens are provided to the App to access the API, and the API to access SQL - in both cases under the user's (i.e. my) identity.
When I publish the API to App Services on Azure, however, and access it there either from a local version of the Web App or an App-Services hosted version of it, the access token that the API gets to access SQL contains the API's Application Identity (system-assigned managed identity), and not the user's identity. Although I can access SQL as the application, it's not what we need.
The Web App obtains its access token using the GetAccessTokenForUserAsync method of ITokenAcquisition - taking as a parameter the single scope I defined for the API.
The API gets its token (to access SQL) like so:
var token = await new AzureServiceTokenProvider().GetAccessTokenAsync("https://database.windows.net", _tenantId)
...where _tenantId is the tenant ID of the subscription.
I have added the SQL Azure Database "user_impersonation" API permission to the AD registration for the API - but that has not helped. As an aside, for some reason Azure gives the full name of this permission as https://sql.azuresynapse.usgovcloudapi.net/user_impersonation - which is slightly alarming as this is just a UK-based regular Azure account.
I have found a few similar posts to this, but mostly for older versions of the solution set. I'm hoping to avoid having to write my own code to post the token requests - this is supposed to be handled by the MSAL libraries.
Should I somehow be separately requesting a SQL access scope from the Web App after sign-in, or should the API be doing something different to get hold of a SQL access token that identifies the user? Why does it work perfectly when running locally?
It seems like this should be a very common use case (the most common?) but it is barely documented - most documentation I've found refers only to the application identity being used or doesn't tell you what to do for this particular tech stack.
Finally - success! In the end this was the critical piece of documentation: Microsoft identity platform and OAuth 2.0 On-Behalf-Of flow - the key points being:
The App only asks for a token to access the API.
The API then requests a token, on behalf of the user identified via the 1st token, to access SQL.
The key is that - since the API cannot trigger a consent window for the second step - I had to use the Enterprise Applications tab in the Azure portal to pre-grant the permissions for SQL.
So the good news is it does work: maybe it's obvious to some but IMO it took me far too long to find the answer to this. I will write up a fuller explanation of how to do this in due course as it can't only be me struggling with this one.
The bad news is that - in the course of my investigations - I found that Azure B2C (which is the next thing I need to add in) doesn't support this "On Behalf Of" flow - click here for details. That's a great shame as I think it's the most obvious use case for it! Oh well, back to the drawing board.
I'm currently working on a similar problem, using a Net5.0 Web app. The reason it appears to be working locally is you are signed into Visual Studio with a user who can access Azure SQL and those are the rights you get in the Db. The IDE is using those credentials in place of the Managed Service Identity, the latter gets used when you upload the app to Azure.
As you noted, in the App registration you need to grant permission to the App for Azure SQL Database user_impersonation.
In your code, you need to request a token from https://database.windows.net//.default (note the // as it's needed for v1 endpoints). By referencing /.default you are asking for all permissions you've selected for the app in the app registration portal.
https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#the-default-scope
In Startup.cs you need to EnableTokenAcquisitionToCallDownstreamApi with the scope you require.
services.AddMicrosoftIdentityWebAppAuthentication(Configuration)
.EnableTokenAcquisitionToCallDownstreamApi(new[]
{"https://database.windows.net//.default"})
// Adds the User and App InMemory Token Cache
.AddInMemoryTokenCaches();
services.AddAuthorization(options =>
{
// By default, all incoming requests will be authorized according to the
// default policy
options.FallbackPolicy = options.DefaultPolicy;
});
services.AddDbContext<MyDatabaseContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("MyAzureConnection")));
// The database interface
services.AddScoped<ITodos, TodoData>();
services.AddRazorPages()
.AddRazorRuntimeCompilation()
.AddMvcOptions(o =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
o.Filters.Add(new AuthorizeFilter(policy));
})
.AddMicrosoftIdentityUI();
You also need to decorate your controllers with [AuthorizeForScopes(Scopes = new string[]{"https://database.windows.net//.default"}] and include the required scopes for that Controller. For Razor, it's at the top of the page model and requires a reference to `using Microsoft.Identity.Web;'
namespace ToDoApp.Pages.Todos
{
[AuthorizeForScopes(ScopeKeySection = "AzureSQL:BaseUrl")]
public class CreateModel : PageModel
I'm using a section in my appsettings.json for the scope and retrieving it using ScopeKeySection:
"AzureSQL": {
"BaseUrl": "https://database.windows.net//.default",
"Scopes": "user_impersonation"
}
This shows you where to include it for MVC, Razor and Blazor:
https://github.com/AzureAD/microsoft-identity-web/wiki/Managing-incremental-consent-and-conditional-access#in-mvc-controllers
Finally, your DbContext needs a token which you could pass to it from the client app (perhaps...).
This is how I am doing it at the moment
public class MyDatabaseContext : DbContext
{
private readonly ITokenAcquisition _tokenAcquisition;
public MyDatabaseContext (ITokenAcquisition tokenAcquisition,
DbContextOptions<MyDatabaseContext> options)
: base(options)
{
_tokenAcquisition = tokenAcquisition;
string[] scopes = new[]{"https://database.windows.net//.default"};
var result = _tokenAcquisition.GetAuthenticationResultForUserAsync(scopes)
.GetAwaiter()
.GetResult();
token = result.AccessToken;
var connection = (SqlConnection)Database.GetDbConnection();
connection.AccessToken = result.token;
}
This is a flawed solution. If I restart the app and try to access it again I get an error Microsoft.Identity.Web.MicrosoftIdentityWebChallengeUserException: IDW10502: An MsalUiRequiredException was thrown due to a challenge for the user
It seems to be related to the TokenCache. If I sign out and in again or clear my browser cache the error is resolved. I've a workaround that signs the app in on failure, but it's deficient since I'm using the app's credentials.
However, it successfully connects to the Azure SQL Db as the user instead of the App with the user's rights instead. When I do solve the error (or find one) I will update this answer.

Azure App service authentication - SQL database client sharding

We have an azure SQL database that will contain multiple client's data. Each table has an account Id which we were planning on using use to seperate client data.
We are displaying the data via an Azure App service and an bff middleware in azure function app. We were planning on adding Azure App Service Authentication to authenticate users into our web app.
However we cannot find documentation on how to store an account Id against an authenticated user; so that we could return results from the database specific only for that user/client?
App Service passes user claims to your application by using special headers. External requests aren't allowed to set these headers, so they are present only if set by App Service.
There are two ways to get the usename(Account id to login).
1.You could use X-MS-CLIENT-PRINCIPAL-NAME as http resquest header to get the username.
var name1=httpRequest.Headers["X-MS-CLIENT-PRINCIPAL-NAME"].ToString();
2.You can retrieve the authenticated user information from the ClaimsPrincipal instance injected in the Run method.
public static async Task<HttpResponseMessage> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]
HttpRequest httpRequest,
ILogger logger,
ClaimsPrincipal claimsPrincipal)
{
var name2 = claimsPrincipal.Identity.Name;
}
After get the username(Account id to login), you can add it to the conditions of the sql statement.
Note:
When you add App registrations in Azure ad, add redirect url as https://yourfunctionname.azurewebsites.net/.auth/login/aad/callback and click ID token when you setting Advanced settings.

Azure ADB2C multiple Web APIs authentication

My scenario is related to the authentication of two or more web APIs from the same MVC Web App in which ADB2C is configured.
I have created two web apis in Azure ADB2C and granted permissions of both the web apis into ADB2C MVC web app. However whenever I tried to obtain the access token, it is giving me the access token for one web api but not giving the access token for the second one.
I want to know whether this scenario is possible in ADB2C or not?
Thanks.
One access token could be used to access by one resource(web api) . If you want another resource's access token , you can use refresh token(if exists) to get the new token :
https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#refresh-the-access-token
If using MSAL.NET ,after initial token acquisition , you can invoke AcquireTokenSilent, asking for the api scopes you need :
// Retrieve the token with the specified scopes
var scope = AzureAdB2COptions.ApiScopes.Split(' ');
string signedInUserID = HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
IConfidentialClientApplication cca =
ConfidentialClientApplicationBuilder.Create(AzureAdB2COptions.ClientId)
.WithRedirectUri(AzureAdB2COptions.RedirectUri)
.WithClientSecret(AzureAdB2COptions.ClientSecret)
.WithB2CAuthority(AzureAdB2COptions.Authority)
.Build();
new MSALStaticCache(signedInUserID, this.HttpContext).EnablePersistence(cca.UserTokenCache);
var accounts = await cca.GetAccountsAsync();
AuthenticationResult result = await cca.AcquireTokenSilent(scope, accounts.FirstOrDefault())
.ExecuteAsync();
MSAL will look up the cache and return any cached token which match with the requirement. If such access tokens are expired or no suitable access tokens are present, but there is an associated refresh token, MSAL will automatically use that to get a new access token and return it transparently.
You can click here for code sample .

SharePoint Provider Apps High Security why Windows Auth

I have setup a SharePoint dev environment and managed to get a provider hosted app working with Certs etc (High Security)
This is all on-premise and we won't have a connection to ACS (now, I may have miss understood ACS, I presume its azure based and servers need to talk to server outside of the server room :) ).
My problem is:
The SharePoint site will not be using Windows Auth, we will be using a login form which will read details from another store.
If I review the code that VS generates I can see that it expects a Windows identity.
Can this be done? I would have expected my provider app not to need any auth as its hosted via SharePoint, it gets the claim from SharePoint so why does it need a Windows Identity as well as the SharePoint Claims.
As you said : The SharePoint site will not be using Windows Auth, we will be using a login form which will read details from another store.
Ans : You are talking about Form based authentication.
If I review the code that VS generates I can see that it expects a Windows identity.
Ans : You are correct. VS generates the code that expects windows identity [For dev environments]. You need to write separate function to get clientcontext for FBA using SharePointContextProvider class.
Context can be :CreateAppOnlyClientContextForSPHost(),CreateUserClientContextForSPHost()
I would have expected my provider app not to need any auth as its hosted via SharePoint
Ans : You can make IIS site [Hosting your provider hosted apps] to allow annonymous authentication.
You can get access token with this code.
var contextTokenString = TokenHelper.GetContextTokenFromRequest(Page.Request);
var appWeb = new Uri(clientContext.Web.Url);
if (contextTokenString != null)
{
SharePointContextToken contextToken =
TokenHelper.ReadAndValidateContextToken(contextTokenString, Request.Url.Authority);
string accessToken =
TokenHelper.GetAccessToken(contextToken, appWeb.Authority).AccessToken;
}
Get current user :-
// Get current context to get load siteurl
var spContext = SharePointContextProvider.Current.GetSharePointContext(HttpContext);
string webUrl =spContext.SPHostUrl.ToString();
//using (var clientContext = spContext.CreateUserClientContextForSPHost())
using (ClientContext clientContext = new ClientContext(webUrl))
{
clientContext.AuthenticationMode = ClientAuthenticationMode.FormsAuthentication;
clientContext.FormsAuthenticationLoginInfo = new FormsAuthenticationLoginInfo(uName, pswd);
Web web = clientContext.Web;
clientContext.Load(web);
clientContext.ExecuteQuery();
// Load SP user from login name found from httpcontext
string currentSPUser = string.Concat("<<<FBADomainPrefix>>>",User.Identity.Name);
var currentUser = clientContext.Web.EnsureUser(currentSPUser);
clientContext.Load(currentUser);
clientContext.ExecuteQuery();
}