How to Authenticate WPF Application with AAD B2C to gain access to Azure App Service - wpf

I'm quite stuck at the moment trying to implement authentication into a project I'm working on. The end goal of this project is to have two WPF apps and on web based app hosted on Azure. One WPF app is for an administrator, the other for staff, and lastly the web app for customers. Each application will be connected to one Azure App Service for a shared database and needs to have authentication so separate all the users. For authentication I am planning on using Azure Active Directory B2C.
I've been researching and trying to implement this for several days now on one of the WPF apps but as I stated before I'm quite stuck. From what I understand, the only way to do B2C authentication for WPF is through client managed authentication. Following the code shown on the Azure tutorial sites, other SO posts, and the Azure Git Repos, I have come up with the following code:
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;
authResult = await App.PublicClientApp.AcquireTokenAsync(App.ApiScopes,
GetUserByPolicy(accounts, App.PolicySignUpSignIn), UIBehavior.SelectAccount,
string.Empty, null, App.Authority);
Newtonsoft.Json.Linq.JObject payload = new Newtonsoft.Json.Linq.JObject();
payload["access_token"] = authResult.AccessToken;
MobileServiceClient msclient = new MobileServiceClient(App.AzureAppService);
MobileServiceUser user = await msclient.LoginAsync(
MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload);
Everything starts off great and I'm able to get my Sign-In policy to display. After logging in, I am given an IdToken and an AccessToken. After creating a JObject and adding the access token to it, I attempt to use it to login with my MobileServiceClient. But that's where I am having issues. No matter what I do, no matter what I try, I only get an exception with a 401 Error telling me I'm unauthorized. And this is the point I've been stuck at for the past few days.
Obviously I'm not doing anything special here and I imagine many people have done this before me but I just can't seem to get past this point and was hoping someone may be able to offer me some guidance. Am I way off track? Is there a better way that I could be doing this? Any suggestions or advice would be greatly appreciated as I am very new to Azure.
Thanks all!
Update:
Here's how I have my Azure Settings:
On the app service side
Client Id: "{Client Id of the AAD B2C App}"
Issuer URL: "login.microsoft.com{TennatName}.onmicrosoft.com/v2.0/.well-known/openid-configuration"
Allowed Token Audiences: "https://{App Service Name}.azurewebsites.net" (App Service URL)
On B2C side:
Web and native client enabled
Web Reply URL: "https://{AppServiceName}.azurewebsites.net/.auth/login/add/callback"
Native App: I did not know what custom redirect URL to have so I have both
"{TennatName}.onmicrosoft.com://auth/" and
"{AppServiceName}.azurewebsites.net/.auth/login/add/callback"
Update 2:
My authority is login.microsoftonline.com/tfp{tenant}/{policy}/oauth2/v2.0/authorize
And my ApiScopes = { "https://{Tenant}/thisisatest/user_impersonation" };

If the authority for the client is set to https://{your-tenant-name}.b2clogin.com/tfp/{your-tenant-name}.onmicrosoft.com/{your-policy-name}/, then the issuer URL in the app service must refer to the metadata for this authority; i.e. https://{your-tenant-name}.b2clogin.com/tfp/{your-tenant-name}.onmicrosoft.com/{your-policy-name}/v2.0/.well-known/openid-configuration.

Related

How to set up access to Azure blob storage from React app (deployed in Azure web app) with credentials from browser?

I got stuck on trying to access Azure blob storage from React app (thus from browser) with credentials.
And firstly I want to admit, that I am newbie in Azure, so maybe I misunderstood some basic concepts...
My current situation and goal:
I am developing React app (lets say MyReactApp). This app uses some files from Azure blob storage (lets say MyBlobStorage) -> it reads, creates and deletes blobs.
I started to develop it on my local and for this dev purpose I was connecting to MyBlobStorage with SAS - this worked perfectly.
MyReactApp is browser only, so it does not have any backend.
After finishing local development, I deployed it as Azure web app with SAS. What have I done:
created App service (lets say MyAppService)
register app in Azure Active Directory and use it as Identity Provider in MyAppService
After this the app works from Azure url perfectly too.
But my app on Azure should fulfill 2 conditions:
User has to log in with AAD account before access to MyReactApp
App itself must get rid of SAS (because it is not secure as it can be obtained from browser) and use some Azure credentials to connect to Azure blob storage
First condition - user log in:
I enabled "easy" logging in MyAppService -> Authentication and chose users, who can have access.
in Authentication section of app in AAD I set up Web type Redirect Uri as /.auth/login/aad/callback
Still everything works great - the user, who is assigned to the app, can log in and work with the app - so far so good, but now the problem comes
Second condition - I wanted to get rid of the SAS to access MyBlobStorage and use DefaultAzureCredentials:
I turned on managed identity for MyAppService and add it as Storage Blob Data Contributor for MyBlobStorage
I obtained user_impersonation and User.Read Api permissions for my app
I removed SAS and tried to add DefaultAzureCredentials to my code -> but it seems, that they can't be used in browser and only option is InteractiveBrowserCredentails
so I tried to use InteractiveBrowserCredentails like this:
this.interactiveBrowserCredential = new InteractiveBrowserCredential({
tenantId: "<appTenant>", // got from app registration on AAD
clientId: "<appClient>", // got from app registration on AAD
redirectUri: <MyAppServiceURi>/.auth/login/aad/callback // the same as in Azure AAD app,
});
this.blobSC = new BlobServiceClient(Constants.STORAGE_PATH, this.interactiveBrowserCredential);
My current result:
This shows login popup after getting to the page and after another signing in it results in error:
AADSTS9002326: Cross-origin token redemption is permitted only for the
'Single-Page Application' client-type.
I googled it of course and according to some answers I tried to change the Web type of redirect URI to SPA.
I tried it, but some other error showed up:
AADSTS9002325: Proof Key for Code Exchange is required for
cross-origin authorization code redemption.
Surprisingly this should be solved by changing from SPA to Web type...:) So I am trapped...
My expected result
In ideal world, I want to connect MyReactApp to MyBlobStorage without popup and with some secret credentials, just to say something like this.blobSC = new BlobServiceClient(Constants.STORAGE_PATH, credentials);
and be able to work with blobs.
Is it possible to access blob storage from browser without errors of course and ideally without popup, which needs another log in for user?
My complementary questions
Can I use somehow the logging info from the user (from his "easy" AAD logging)? I can get his info with GET call to /.auth/me, maybe it can be utilized, so I can use his info to access the blobs?
The solution should be working on localhost too (I tried to add http://localhost:3000/ to redirect uri, but without success), can it be done?
Thank you all, who read the whole story!

Trouble authorizing access to App Engine via IAP

I currently have App Engine up and running, protected by IAP, and my eventual aim is to have this be triggered by an Apps Script project. I've tested the code without IAP and it works fine. However, I'm running into difficulties successfully authorizing access to it when IAP is enabled.
I've added myself as an IAP-secured Web App User (as well as Policy Admin) to the App Engine, but whenever I try triggering it from a GSheets Apps Script where I'm the owner and it's associated with the correct GCP project (using this great explanation as a guide) I get the following:
"Invalid IAP credentials: JWT audience doesn't match this application ('aud' claim (1084708005908-bk66leo0dnkrjsh276f0rgeoq8ns87qu.apps.googleusercontent.com) doesn't match expected value (1084708005908-oqkn6pcj03c2pmdufkh0l7mh37f79po2.apps.googleusercontent.com))"
I've tried adding/removing various permissions to my account, as well creating a new Apps Script and re-adding to the project, but to no avail. I run into the same issue when triggering from CLI, so I'm fairly sure it's an issue with authentication, however this is my Apps Script code in case it helps:
function test() {
const options = {
headers: {'Authorization': 'Bearer ' + ScriptApp.getIdentityToken()},
muteHttpExceptions: true
}
var result = UrlFetchApp.fetch('https://APP-ENGINE-URL.appspot.com', options);
Logger.log(result);
}
And the manifest file:
{
"timeZone": "Europe/London",
"dependencies": {
},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"oauthScopes": ["openid", "https://www.googleapis.com/auth/script.external_request"]
}
Any help on this is super appreciated! Never posted here before, but pretty desperate and couldn't find anyone with this exact problem on SO.
Consideration
The problem with your solution is that you are using the identity of an auto-generated OAuth Client for Apps Script. This clients are not suitable for this kind of authentication, here a complete list of supported OAuth clients.
Solution
In order to complete your authentication you will need an extra step. You will have to create another OAuth Client and build an identity token with its credentials.
To make things easier I would recommend to use this Apps Script library: https://github.com/gsuitedevs/apps-script-oauth2
The inital Set-up is covered in the linked documentation.
Important: When creating the OAuth Client take note of the ClientID and the Client-secret. Plus, you will need to add an Authorized Redirect URI. This is standard when using the OAuth2 GAS library and it has this form: https://script.google.com/macros/d/{Your Apps Script ID}/usercallback
Now you have all the necessary information to build your identity token. In the Github repository there is a boilerplate sample that will cover the first coding steps with the OAuth2 GAS library.
Here is the link.
Copy this code to your Apps Script project and follow the instructions in the comments. You will need to add an extra OAuth scope: "https://www.googleapis.com/auth/userinfo.email".
Once you set all the constants with your OAuth clients information you should run the run() function from your Apps Script editor. This will log a URL you have to open in your browser to authorize your App. Once you authorized the App run again the run() function and you will successfully access your IAP protected application.
References
OAuth2 GAS library
IAP programmatic authentication

How does AAD API Access delegate permission work?

I'm having a little trouble following how API Access delegate permissions work with azure active directory. I feel like i'm probably misunderstanding a key aspect of how AAD works.
Here is my set up
I have a Web Application let’s call it WebApp. I have created
an AAD for the Web Application and registered with a AAD App ID. Let’s
call it App ID A
I have a Web Api let’s call it ApiService. I have also created an AAD for it and registered with a AAD App ID. Let’s all it App ID B.
In AAD App ID A, I have updated the clicked on the API Access ->
Required Permissions -> Add (App ID B ; Web API) permissions
I’ve updated the manaifest in the AAD App ID B, to give consent to
knownClientApplications to include the client ID of the Web App
I’ve also enable oauth2AllowImplicitFlow to be true for both App’s
manifest.
What I’m trying to do is, A user signs into the web application sign. When it signs in, the user is able to acquire a token for the specific Web App App ID A. The user should be able to use that token and have access the Api Service with App ID B. I thought by configuring the whole API Access -> Required Permissions within the Web Application it would give me delegate permission with the logged in user to communicate with the Api Service WebApi.
When I examine the JWT token, I notice that there is a claim for Microsoft Graph, but not for the ApiService. Shouldn’t I be seeing a claim?
When I try to use the token, it reacts with a 404 authentication error.
Any advice appreciated,
Thanks,
Derek
UPDATE
In response to #joonasw
I actually looked at the example you wrote when i started.
https://joonasw.net/view/aspnet-core-2-azure-ad-authentication
In the example, the web application is initialized with:
.AddOpenIdConnect(opts =>
{
Configuration.GetSection("OpenIdConnect").Bind(opts);
opts.Events = new OpenIdConnectEvents
{
OnAuthorizationCodeReceived = ctx =>
{
return Task.CompletedTask;
}
};
});
In the HomeController, there is code to retrieve the token for the graph api
private async Task<string> GetAccessTokenAsync()
{
string authority = _authOptions.Authority;
string userId = User.FindFirstValue("http://schemas.microsoft.com/identity/claims/objectidentifier");
var cache = new AdalDistributedTokenCache(_cache, _dataProtectionProvider, userId);
var authContext = new AuthenticationContext(authority, cache);
//App's credentials may be needed if access tokens need to be refreshed with a refresh token
string clientId = _authOptions.ClientId;
string clientSecret = _authOptions.ClientSecret;
var credential = new ClientCredential(clientId, clientSecret);
var result = await authContext.AcquireTokenSilentAsync(
"https://graph.microsoft.com",
credential,
new UserIdentifier(userId, UserIdentifierType.UniqueId));
return result.AccessToken;
}
From my understanding, when the user initially login to the web application it will trigger the OnAuthorizationCodeReceived() method where it will be using the clientId/clientSecret/resource of the web applicaiton. The token is stored in the distributed token cache under the key resource/client id.
In the example, GetAccessTokenAsync() is used to grab the token to access the graph API.
In my case, I was hoping to update that method to retrieve the token for the WebApi which has a different clientId/clientSecret/resoruce. In my case, it will AcquireTokenSilentAsync will throw an AdalTokenAcquisitionExceptionFilter because the token needed is not stored in the cache and in the AdalTokenAcquisitionExceptionFilter it will call try to reauthenticate
context.Result = new ChallengeResult();
which will redirect to the authentication page and then hits the AddOpenIdConnect() method. However, the openIdConnect is configured with the web app clientID/ClientSecret/Resource and will not store the new token properly. It will try to call GetAccessTokenAsync() again and the whole process will go in an infinite loop.
In the example, if you were to comment out the "Anthentication:resource" in app.settings, you will experience the same issue with the infinite loop. What happens is that you initially authenticate correctly with no resource specified. Then when you click on you try to get the token for microsoft graph which is a new resource, it can't find it in the cache and then tries to reauthenticate over and over again.
I also notice that the acquireAsyncAuthentication only returns a AuthenticationResult with a bearer tokentype. How would you get the refresh token in this case?
Any advice?
Thanks,
Derek
UPDATE (Solution)
Thanks to #jaanus. All you have to do is update the resource to the clientid of the web api and pass that into AcquireTokenSilentAsync. The web api id uri that you can get from the azure portal did not work.
Okay, so it seems there are multiple questions here. I'll try to make some sense of this stuff to you.
Adding the "Web App"'s client id to the "ApiService" knownClientApplications is a good idea.
It allows for consent to be done for both apps at the same time. This really only matters for multi-tenant scenarios though.
Now, your Web App will be acquiring access tokens at some point.
When it does, it must specify a resource parameter.
This parameter says to AAD which API you wish to call.
In the case of the "ApiService", you should use either its client id or Application ID URI (this is more common).
Depending on the type of your Web App, the access token is acquired a bit differently.
For "traditional" back-end apps, the Authorization Code Grant flow is usually used.
In this flow your back-end gets an authorization code after the user logs in, and your Web App can then exchange that code for the access token.
In the case of a front-end JavaScript app, you would use the Implicit Grant flow, which you have allowed (no need to enable it in the API by the way).
This one allows you to get access tokens directly from the authorization endpoint (/oauth2/authorize) without talking to the token endpoint as you usually have to.
You can actually get the access token right away after login in the fragment of the URL if you wish.
ADAL.JS makes this quite a lot easier for you if you are going in this route.
The reason you get the authentication error is because the access token is probably meant for Microsoft Graph API. You need to request an access token for your API.
An access token is always only valid for one API.

Can someone explain HttpContext.Authentication.GetTokenAsync("access_token")

I have implemented an ASP.NET Core MVC Client using Hybrid flow, and I am wondering what the HttpContext.Authentication.GetTokenAsync("access_token") does.
If you need more background on my question:
The instructions for accessing an API from with an ASP.Net Core Client App Controller Action are generally as follows:
var accessToken = await HttpContext.Authentication.GetTokenAsync("access_token");
var client = new HttpClient();
client.SetBearerToken(accessToken);
var response = await client.GetAsync("http://localhost:5001/api/stuff");
There is magic in httpContext.Authentication.GetTokenAsync("access_token") :-)
I am wondering what this function might be doing. Is it decrypting the access token from a cookie in the MVC App Domain? ... from the ID4 Domain?
I am sorry but I have been unable to find sufficient documentation on what this does or finding the cookie the access token may be in. I have looked here:
https://learn.microsoft.com/en-us/aspnet/core/api/microsoft.aspnetcore.authentication.authenticationtokenextensions
Does anyone know what it does? A link to more thorough documentation is a totally appreciated answer.
TU!
You can store arbitrary tokens in your authentication cookie in and that method simply returns one with a given name. Actually setting that would have happened during the sign in process. So in short, it comes from the authentication cookie for your client application and would typically be set at the point of sign in using IdSrv4.
It's a way for the client webapp to confirm separately whether the user really did authenticate with IdentityServer (the user comes to your webapp, the webapp forwards the user to IdentityServer for authentication). The id_token comes back with the user to your webapp in a browser cookie, basically when your webapp sees that id token, it does an independent check with IdentityServer, and if it represents a valid user authentication, you'll get an access token back. You can use that access token to get user claims from the userinfo endpoint.
After all of the above, you can take one of the claims and use it as a key for your application's roles/permissions.

Error AADSTS65001 but no consent prompt for just one user

I am writing a C# .NET app. It is connected to our service on Azure which is configured for using AAD. In turn, our service tries to make calls to Exchange via EWS.
This all worked fine for me until we recently deployed our service bits to a new Azure web app with new app registrations. They are all configured correctly and other developers on our team can authenticate with the service and use it as expected.
When I try to connect to the service, I get the following error:
AADSTS65001: The user or administrator has not consented to use the application with ID '61a8b794-7f67-4a01-9094-fcdd45693eaa'. Send an interactive authorization request for this user and resource.
Trace ID: ece7c5d0-2ecb-4096-a87a-2cd33271d65d
Correlation ID: 093b5935-3b06-4d76-91a9-6619bc179544
Timestamp: 2017-02-09 23:19:28Z
The consent prompt never appeared for me when trying to connect after deploying the new service.
I'm not sure what it is about my user account that causes this error to occur (it happens on multiple machines with my account) while others can connect successfully.
Here’s some of the code used to acquire the token in the service:
var bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext as System.IdentityModel.Tokens.BootstrapContext;
var upn = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn);
var email = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Email);
var userName = upn != null ? upn.Value : email?.Value;
accessToken = bootstrapContext.Token;
ClientCredential clientCred = new ClientCredential("61a8b794-7f67-4a01-9094-fcdd45693eaa", appKey);
UserAssertion assertion = new UserAssertion(accessToken, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com/microsoft.onmicrosoft.com");
AuthResult = authContext.AcquireToken("https://outlook.office365.com", clientCred, assertion);
Any ideas why I wouldn't get the consent prompt, but other users on my team have?
Based on the description, you are developing multi-tier application using Azure AD.
Since you mentioned this issue was occurred after using the new app, did you config your new app as the knownClientApplications of your service app(61a8b794-7f67-4a01-9094-fcdd45693eaa)?
If yes, you should can give the consent for the service app when you sign-in your web app( refer here about multi-tier applications).
The problem why only you get this issue may others have given the consent to this app before.
Please let me know if it helps.

Resources