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.
Related
I need service to service authentication.
I followed this documentation Azure AD B2C s2s
and everything looks good so far except for I am not able to acquire access token for client service in any other way rather than just explicitly calling for POST authentication endpoint.
So I am able to retrieve access token doing in code something like this:
var bodyContent = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "client_credentials"),
new KeyValuePair<string, string>("client_id", "xxx"),
new KeyValuePair<string, string>("client_secret", "xxx"),
new KeyValuePair<string, string>("scope", "https://xxx/.default")
};
var result = await _httpClient.PostAsync("auth_url", new FormUrlEncodedContent(bodyContent));
var accessToken = await result.Content.ReadFromJsonAsync<AccessTokenResponse>()
What I was trying to achieve is trying make it work automatically in some way using:
builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, Constants.AzureAdB2C)
.EnableTokenAcquisitionToCallDownstreamApi(new string[] { builder.Configuration["MyScope"] })
.AddInMemoryTokenCaches();
and then acquire token like this:
string result = await tokenAcquisition.GetAccessTokenForUserAsync(scopes);
instead of doing it manually like in the example above.
But whatever I am trying to do I still get No account or login hint was passed to the call
Update 02/11/2022
Seems like you're simply using the wrong method to request an authorization code
When you want a (daemon or app) service to authenticate to another service:
Don't use GetAccessTokenForUserAsync(); // Wrong, it uses the current logged in user for the request
Do use GetAccessTokenForAppAsync() // Correct, it request a code via client_credentials flow
Short answer: You're passing in scopes to .EnableTokenAcquisitionToCallDownstreamApi() instead of the AzureAD OpenIdConnect configuration with a separate app registration attached to it.
The long answer:
"The OAuth 2.0 client credentials grant flow permits an app (confidential client) to use its own credentials, instead of impersonating a user, to authenticate when calling web resource, such as REST API." [Microsoft Docs]
This flow does not authorize with a user who initiated the request, it uses its own credentials.
Therefore, you need 3 app registrations in AzureAD B2C.
[1] The front-end app registration where your users sign in
[2] The app registration you will use to generate authorization codes and
authorize scopes
[3] The app registration for the service you're trying to call.
With that in place you add a new section to your AppSettings.json in the front-end (eg. name it "downstreamApi"), containing the client secret, scopes, and some default OIDC settings like Authority, and then you can then simply call
services
.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.EnableTokenAquisitionToCallDownstreamApi()
.AddDownStreamWebApi("MyAPI", Configuration.GetSection("downStreamApi");
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.
Our senior dev left us recently and i'm trying to swim into the ocean that is IdentityServer.
There is a few things I don't understand and can't find informations on it.
Like for example, when you run the client application, it is redirrected to IdentityServer/Account/Login with a parameter called returnUrl.
That returnUrl seems to be used for many things, like retrieving the authorization context with :
await _interaction.GetAuthorizationContextAsync(returnUrl);
My first question would be :
From where is that returnUrl coming from? how/where is it created?
Second question is :
Is there a way to store it somewhere in the client application after you sign-in?
I mean, there is a point in the client application where we have to send mails to create new accounts.
Those mails redirrect new users in a "register" page in IdentityServer.
What i'd like to do is to have the returnUrl here to redirrect the new user to the correct client application.
Thanks for your time !
EDIT :
here is the return url set in my Client (in IdentityServer) :
RedirectUris = {"http://localhost:44349" + "/signin-oidc"}
and here is the returnUrl recieve by the Login method in IdentityServer when i start the client application
"/connect/authorize/callback?client_id=MyApplication_Web&redirect_uri=http%3A%2F%2Flocalhost%3A44349%2Fsignin-oidc&response_type=code%20id_token&scope=openid%20offline_access%20roles%20Cri_Identity_Serveur_Api.Full_Access&response_mode=form_post&nonce=637230882720491348.YmQzODA4ZTQtNDczZS00OWYwLWFmNmEtYjA2NmQ3YmIwZjg2YzdiODk4ZDYtMTU1YS00ZTM0LWE0MGEtNDVjOWNkZWFiYTM1&state=CfDJ8Ly2XCz96vdGkR4YQuJ4jeE-v9P4l1W7fLWpJcCGZpt1rMpXyWqEGdnaeRZfiZy4M4Z79LcixUbo06zImhsxwbgyV4hK82qmn0mI6wkrxwraT1tH3XNCdSXCfUJqwk_hZguMSwspZDEN6r1WxnZsU9kT8MHrb9qpzsMOMzsotVzToEjgMtxIeoRfqFSoK8ZfUXBkSw__qxVyIe1lCs96-I--ufZSyO2pBe2kfau-ah7eR5-9oopxX6x1k0tzFHAk6Y3_jMqGysES_GmmfeUJvXXFIR35Rc-IaxU1igswmL2h1IUS-0DQ98Tv_Gf3hirnS87SU87aSJhajgn2YmARXWc&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=5.3.0.0"
it seems that to retrieve the autorization context using await _interaction.GetAuthorizationContextAsync(returnUrl); the returnUrl must be the entire url with "/connect/authorize/callback?..."
Unfortunatley Martin's answer is incorrect - returnUrl in this case is the authorize endpoint URL that was originally requested by the client. If the user is not authenticated then that endpoint will redirect to your nominated sign in UI endpoint and include this param. GetAuthorizationContextAsync() simply analyses that URL and if it's matches the signature of an authorize endpoint URL then it will parse, validate and return you an object representing that original request. You can then customise your sign in flow based on that info (e.g. show the name of the client you're signing into or restrict what social sign in methods are available or any number of other things).
You could include this URL in your sign up flow but personally I'd favour a single-use code based email verification flow and that means the user doesn't lose context if for example signing into a native mobile app and also doesn't need an email client on the device they're signing up on - e.g. a phone which doesn't have access to their work email account. Everyone's requirements are different though.
From where is that returnUrl coming from? how/where is it created?
The return URL is specified as redirect_uri by the client who calls Identity Server. It is a callback address.
Clients are configured in Identity Server and for each configured client it is configured a list of allowed redirect URIs to return tokens or authorization codes to with RedirectUris property of the Client class. The configuration is in a class called Config which has method GetClients.
public class Config
{
public static IEnumerable<Client> GetClients(IdentityServerAppSettings settings)
{
return new[]
{
new Client
{
ClientId = "roivenue-frontend",
RedirectUris = new [] { "https://myappurl/somepage.html" }, // list of allowed URLs
...
},
If the address sent by the client as redurect_uri does not match any of the configured addresses, the request validation fails in Identity Server.
Is there a way to store it somewhere in the client application after you sign-in?
The callback address is in the client application you are developing. It can be any address that is known to Identity Server with the configuration above.
I am creating a xamarin mobile application and I want to create new users from the mobile application , I know I can create users from sync gateway configuration or from admin REST API ,
I tried to use the admin REST API in the application but I got Java.Net.SocketTimeoutException
so how can I use the admin REST API to create users in the application ?
Here in the code I am using :
private async void CreateUserBtnClicked(object sender, EventArgs e)
{
HttpClient _client = new HttpClient(); //Creating a new instance of HttpClient. (Microsoft.Net.Http)
content = "{\"Username\":\"user.com\",\"Password\":\"123456\"}";
string url = "http://<server_ip>:4985/user/";
var request = await _client.PostAsync(url, new StringContent(content, Encoding.UTF8, "application/json"));
}
In Sync Gateway, port 4985 is intentionally not public by default. Opening it allows anybody full access to your data.
Integrate with a third-party authentication provider using OpenID Connect. You can configure these to create users on-demand when they sign in.
https://docs.couchbase.com/sync-gateway/current/authentication.html#openid-connect
Write your own small service that can take new user requests from your app, which then calls out to a firewalled Sync Gateway Admin API.
https://docs.couchbase.com/sync-gateway/current/authentication.html#custom-authentication
Scenario:
Both Web application and Web API need to be authenticated and protected from the server side.
Requirement:
Web application is serving the contents for the browser and browser should be calling Web API directly (i.e. Browser to API).
Question:
Is it possible to authenticate both Web APP and the API using tokens?
Any sample code or clear direction would be highly appreciated.
Normally web applications are authenticated using cookies and APIs are authenticated using tokens.There are some sample projects available here but they are either browser to API (SPA token based) or Server side Web App calling API from server to server.
UPDATE 1
App is saving the TokenValidationParameters and used bootstrapContext.Token within the app controller to grab for server to server communication.
As per #dstrockis, I'm trying to grab the id_token from the Web App soon after the end of validation (not within the app contrller).
I'm using SecurityTokenValidated invoker in OpenIdConnectAuthenticationOptions.Notifications within the Startup class. SecurityTokenValidated receives a parameter of type SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> but I'm not sure where to find the id_token within it. Method is below.
private OpenIdConnectAuthenticationOptions CreateOptionsFromPolicy(string policy)
{
return new OpenIdConnectAuthenticationOptions
{
// For each policy, give OWIN the policy-specific metadata address, and
// set the authentication type to the id of the policy
MetadataAddress = String.Format(aadInstance, tenant, policy),
AuthenticationType = policy,
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = clientId,
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
//NEW METHOD INVOKE ************************************
//******************************************************
SecurityTokenValidated = OnSecurityTokenValidated
//******************************************************
},
Scope = "openid",
ResponseType = "id_token",
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
SaveSigninToken = true
},
};
}
//NEW METHOD ************************************
private Task OnSecurityTokenValidated(
SecurityTokenValidatedNotification<OpenIdConnectMessage,
OpenIdConnectAuthenticationOptions> arg)
{
//QUESTION ********************************************************
//How to find the just saved id_token using incoming parameter, arg
//*****************************************************************
return Task.FromResult(0);
}
UPDATE 2
Instead of SecurityTokenValidated, I tried AuthorizationCodeReceived and it's not getting called at all. As discussed here, my redirect url does have an ending slash as well.
Any Ideas?
Our ASP.NET OpenID Connect middleware which supports AAD B2C is built to rely on cookie authentication from a browser. It doesn't accept tokens in a header or anything like that for securing web pages. So I'd say if you want to serve HTML from your web app in the classic way, you need to use cookies to authenticate requests to the web app.
You can definitely get & store tokens within the browser and use those to access your web API, even if you use cookies to authenticate to the web app. There's two patterns I'd recommend:
Perform the initial login using the OpenID Connect Middleware, initiating the flow from the server side as described in the samples. Once the flow completes, the middleware will validate the resulting id_token and drop cookies in the browser for future requests. You can instruct the middleware to save the id_token for later use by using the line of code written here. You can then somehow pass that id_token down to your browser, cache it, and use it to make requests to the API.
The other pattern is the inverse. Start by initiating the login from javascript, using the single page app pattern from the B2C documentation. Cache the resulting id_tokens in the browser, and use them to make API calls. But when the login completes, you can send a request to your web app with the id_token in the body, triggering the OpenID Connect middleware to process the request and issue a session cookie. If you want to know the format of that request, I'd recommend inspecting a regular server side OpenID Connect flow.
Found the answer to my own question and adding here for the future reference.
After a successful validation, id_token can be accessed by invoking the SecurityTokenValidated notification. Code sample is below.
private Task OnSecurityTokenValidated(
SecurityTokenValidatedNotification<OpenIdConnectMessage,
OpenIdConnectAuthenticationOptions> arg)
{
//Id Token can be retrieved as below.
//**************************************
var token = arg.ProtocolMessage.IdToken;
return Task.FromResult(0);
}
However, saving this directly into a browser cookie may not be secure.