I'm using my organization's Azure AD to authenticate users on a corporate web app. I intend for this to be a single-tenant application. When I run it, I'm prompted to log in with my organization's credentials, as expected. On submitting my credentials, however, I get this error:
Sorry, but we’re having trouble signing you in.
AADSTS50194: Application 'appId'(appname) is not configured as a multi-tenant application. Usage of the /common endpoint is not supported for such applications created after '10/15/2018'. Use a tenant-specific endpoint or configure the application to be multi-tenant.
The thing is, I'm not trying to use the /common endpoint. Here is the relevant bit of my appsettings.json:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "COMPANYDOMAIN.onmicrosoft.com",
"TenantId": "MYTENANTID-xxxx-xxxx-xxxx-xxxx",
"ClientId": "MYCLIENTID-yyyy-yyyy-yyyy-yyyy",
"CallbackPath": "/signin-oidc"
},
And here's the app's settings in the Azure portal (Home>AppRegistrations>App>PlatformConfigurations>Authentication):
My startup.cs, which I assumed set the endpoint in question, is taken directly from the Microsoft-provided sample:
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.Authority = options.Authority + "/v2.0/";
options.TokenValidationParameters.ValidateIssuer = false;
});
I've also set false to true for kicks, but the result was the same.
What am I doing wrong, here? Why does Azure AD continue to believe I want to use the /common endpoint?
Edit: As I continued to search, I happened on #jack-jia's answer here: Application is not configured as a multi-tenant application
I haven't quite solved my issue, but their answer offered some promising clues.
Since you have configured it as single tenant application on Azure portal, this issue must occurred due to the wrong authorization endpoint. Please check the value of options.Authority again. If it is identified as single tenant, you can use fiddler to capture the request, the request is something like
GET https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?
client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&response_type=id_token
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
&response_mode=form_post
&scope=openid
&state=12345
&nonce=678910
Check if the endpoint is correct.
I came here with the same error but using python to authenticate my API calls.
Specifically I was using authOAuthDesktopMobileAuthCodeGrant class from bingads.authorization
The solution I ended up with is as follows:
The init for authOAuthDesktopMobileAuthCodeGrant shows the following, note that tenant is defaulted to "common".
def __init__(self, client_id, oauth_tokens=None, env=PRODUCTION, oauth_scope=MSADS_MANAGE, **tenant='common'**):
Therefore, in order to overwrite /common like others here have done. I had to set this tenant component here to my app's tenant ID
authentication = OAuthDesktopMobileAuthCodeGrant(
client_id=client_id,
tenant="{YOUR_TENANT_ID}",
env='production'
Hope this is useful to others who are working out of python.
Related
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.
I have an ASP.NET Core 3.1 Razor application. Sometimes, I get the following exception when the application redirects to /signin-oidc when I'm not authenticated after signing out:
OpenIdConnectProtocolException: Message contains error: 'access_denied', error_description: 'AADSTS50105: The signed in user '{EmailHidden}' is not assigned to a role for the application '<myClientApplicationID>'
I have a feeling like it could be trying to automatically use a different Azure AD account since I have several AD accounts for various, unrelated, clients. The URL that triggers this issue is https://localhost:5000/signin-oidc and prevents me from logging in using my Azure AD account. This does not happen all the time, but when it does, I don't know how to trigger the standard Microsoft login screen so that I can sign in using my valid credentials. If this is true, then I wonder if there is a way to prevent it from attempting to use any account other than the ones that configured for the tenant.
How can this exception be handled/configured so that it will force the user to login using their Azure AD account? Below you will find the startup code for authentication.
services
.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
And here are the options in my appsettings.json
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "mydomain.org",
"TenantId": "<myTenantId>",
"ClientId": "<myClientId>",
"CallbackPath": "/signin-oidc"
}
You can follow these steps to assign application roles to your users.
1.
2.
3.
4.when you click 'Select', then click on the 'assign' in the lower left corner, and you'll see that the assignment has been completed.
For more information, please refer to this.
I've been playing with AzureAD B2C using ASP.NET Core 3.1
I've already set up an AzureAD B2C using the project wizard and it's worked well. I now need to retro fit an existing application to use AzureAD B2C.
I've imported the same package from the existing application, ie..
I've setup the appsetting.json file ...
"AzureAdB2C": {
"Instance": "https://xxx.b2clogin.com/tfp/",
"ClientId": "xxx-xxx-xxx-xxx-xxx",
"CallbackPath": "/signin-oidc",
"Domain": "xxx.onmicrosoft.com",
"SignUpSignInPolicyId": "B2C_1_SignInRegister",
"ResetPasswordPolicyId": "B2C_1_PasswordReset",
"EditProfilePolicyId": "B2C_1_ProfileEdit"
},
Added the services in ConfigureServices..
services.AddAuthentication(AzureADB2CDefaults.AuthenticationScheme)
.AddAzureADB2C(options => Configuration.Bind("AzureAdB2C", options));
and added Auth in Configure
app.UseAuthentication();
app.UseAuthorization();
For a moment, I see my app shooting over to azure for authentication, but then I get redirected back to https://localhost:xxxx/signin-oidc, with a ERR_CONNECTION_CLOSED
I've missed something somewhere.
I fixed this myself, it was an oversight when I set up the new project.
As is typically the case with Visual Studio when using IIS Express to Debug, the new project used a different ssl port. I needed to enter the new port with the Azure AD B2C App Registration.
Note the correct address and port in VS
Go to the Azure Portal, bring up the B2C Overview for the relevant Domain
Click on "App Registrations"
Select the relevant Application
Select the redirect uri's link
Click "Add URI", and enter the correct signin-oidc link, for me, that was https://localhost:44304/signin-oidc
I'll need to remember that when I eventually publish the code. I found that I could enter as many uri's as I needed. Eg, one for DEV, one for QA, one for PROD etc....
We have an ASP Core 2.0 App working nicely with Azure AD on the private network. However, we've been playing around with the Azure Application Gateway, investigating the possibility of allowing access to the app from outside for remote workers etc.
We have registered the app on the Gateway, and, once logged in with Azure AD, the anonymous front page is accessible via ourapp.msappproxy.net. However, when signing in (again?) in the app, the client is redirected back to intervalServer/signin-oidc which fails as it is not accessible externally.
While I doubt this is any part of the solution, I have tried overriding the redirect "CallbackPath": "/signin-oidc", to absolute path ourapp.msappproxy.net/signin-oidc but I can't seem to work out how. Changing the reply URL in Azure Portal doesn't help either (although I doubted it would, this is just for verification right?).
I can't seem to find any guidance on this on this particular scenario, so that would be welcome. Otherwise, I'm left pondering the following:
1, If I could change the redirect to ourapp.msappproxy.net/signin-oidc, would that solve the sign in issue?
2, Do I even need an additional sign in step, or should I be changing the app to accept AzureAppProxyUserSessionCookie or AzureAppProxyAccessCookie? (If that's even an option?)
Thanks to rfcdejong in the comments for putting me on track. In our case I was able use Azure AD with the Azure Application Gateway by overriding OnRedirectToIdentityProvider event and supplying the proxy url in ConfigureServices
services.AddAuthentication(...)
.AddOpenIdConnect(options =>
{
options.ClientId = Configuration["Authentication:AzureAD:ClientId"];
options.Authority = Configuration["Authentication:AzureAd:Authority"];
options.CallbackPath = Configuration["Authentication:AzureAd:CallbackPath"];
if (IsProduction) // So that I can use the original redirect to localhost in development
{
Task RedirectToIdentityProvider(RedirectContext ctx)
{
ctx.ProtocolMessage.RedirectUri = "https://ourapp.msappproxy.net/signin-oidc";
return Task.FromResult(0);
}
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = RedirectToIdentityProvider
};
}
})
The return URI needs to be configured to match for the app in Azure Portal.
Users also need to be assigned, but the internal app is now available anywhere without requiring direct access to the server.
I'm attempting to setup a prototype API using nodejs that uses 3Scale's API management.
I've been able to find their plugin intergration code, which is as follows:
var ThreeScale = require('3scale').Client;
// keep your provider key secret
var client = new ThreeScale("X");
// you will usually obtain app_id and app_key from request params
client.authrep({ app_id: "Y",
app_key: "Z" }, function(response){
if(response.is_success()) {
// continue
} else {
throw new Error("not authorized " + response.error_message);
}
});
Which makes some sense to me as part of a server module. But, I'm not sure where the client's credentials are in that equation....
I see it as the client is pointing to your app, and here's the password for the app...but what about the username/password for the actual client!? where does that get checked?
I feel like I'm not grasping their architecture (possible because it's my first real node project and definitely my first time using 3Scale)...
Further, what's a client's request then look like?
in 3scale system app_id and app_key (in this authentication method) kind of represent the user's (i.e. developer's) credentials. This is due to the fact that every user can have more than one application and one application belongs just to one user, so you don't need user credentials. The credentials are checked on the 3scale system side and if authorized, they report the usage and forward the call to your API.
provider_key identifies your account (API owner) and you have to keep it secret (if someone gets it, they can impersonate you).
Did you already check the 3scale's support site? There are many useful information on the system architecture, some tutorials on integration, etc.
You can check them here: http://support.3scale.net
btw. the node.js plugin is a community plugin. You can also try integration via nginx reverse proxy.