How to Login and Authenticate Microsoft Graph B2C User across ASP Net Core2, VueJS, Xamarin.Forms and Azure Functions C# precompiled (v1 and v2)? - azure

I am developing a portal, and have this scenario:
The user enters the ASP.Net Core 2 Razor Pages Web portal, signup or sign-in with Facebook
On the main page, index.cshtml, there is HTML and a simple VueJS with a property {{ Account.Total }}
The VueJS brings this Account.Total value from an Azure Function using Axios Javascript library. https://myfunctionapp.azurewebsites.net/api/GetAccountTotal?AccountId=ABC
User also logs in UWP and Mobile Xamarin.Forms App
The Mobile App, made in Xamarin.Forms also calls this Azure Function to get the Account.Total
The UWP App also calls this Azure Function to get the Account.Total
The idea is that the ASP.Net Core would be just a Client, like the UWP, and Xamarin App. The Azure Functions would be the backend, like the Web API; The Microsoft Graph Facebook Authentication would secure all this and identify the User.
The Azure Function need to use the same Graph/Azure AD B2C Authentication. The user will login just one time at the ASP.Net Razor Page, and all related services and calls must read these credentials.
How to configure, and what code is needed to do this Login and Authenticate Microsoft Graph B2C Facebook User Account across ASP Net Core2, VueJS and Azure Functions C# precompiled (v1 and v2)?

All this look obvious, but after some days trying, I will post here the steps.
(WIll edit this answer to put more details)
But the most important difficult to find part (in order) are:
1) It worked after I added the additionalLoginParams
"additionalLoginParams": ["response_type=code id_token", "resource=<app_id>"]
on https://resources.azure.com/
At the top of the page, select Read/Write.
In the left browser, navigate to subscriptions > resourceGroups > > providers > Microsoft.Web > sites > > config > authsettings.
Click Edit.
Modify the following property. Replace with the Azure Active Directory application ID of the service you want to access.
2) On ASP.Net Core 2 you put Login and Logout:
LOGIN<br />
LOGOUT<br />
and you can request
string UserName = (string)Request.Headers["X-MS-CLIENT-PRINCIPAL-NAME"];
string UserId = (string)Request.Headers["X-MS-CLIENT-PRINCIPAL-ID"];
string Token = Request.Headers["X-MS-TOKEN-AAD-ID-TOKEN"];
3) Then on the JavaScript, you must Call the Azure Function sending the Authorization Header Bearer Token
function WhoAmI() {
var Token = "#ViewData["Token"]";
var url = "https://MyFUNCTION.azurewebsites.net/api/WhoAmI";
var headers = { headers: { "Authorization": `Bearer ${Token}` } };
//if (Token == "") headers = null;
axios.get(url,
headers
).then(response => {
console.log("WhoAmI()");
console.log(response.data);
});
}
Sources:
https://learn.microsoft.com/en-us/azure/app-service/app-service-authentication-how-to#refresh-access-tokens
Maybe I will also need to use
0) ADAL.NET (Microsoft.IdentityModel.Clients.ActiveDirectory)

Related

How to validate request with token in .net core 2.2 api using Open Id Connect

I have a .net core 2.2 api setup and deployed to Azure. I have used OpenId Connect to handle the authentication using azure active directory single tenenat using the code below. And the Authorize decorator on my controller. Everything works and when I browse to my azure deployed api (myappname.azurewebsites.net) I get microsoft login prompt. I'm able to then login and view my route data.
services.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
auth.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
auth.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(opts =>
{
Configuration.GetSection("OpenIdConnect").Bind(opts);
opts.Events = new OpenIdConnectEvents
{
OnAuthorizationCodeReceived = ctx =>
{
return Task.CompletedTask;
}
};
opts.Scope.Add("openid");
opts.Scope.Add("profile");
opts.Scope.Add("access_as_user");
});
The problem is that when I have the Authorization turned on for my controller, I am not able to call it form my angular SPA client application. I have successfully configured MSAL and my api calls are passing a token. However, I get the following error:
https://login.microsoftonline.com/bit4f574-5968-4a40-049d-1c0dc2ca0513/oauth2/authorize?client_id=caor847f-dd19-4489-bef7-684803728c665&redirect_uri=https%3A%2F%2Fmyapi.azurewebsites.net%2Fsignin-oidc&response_type=code%20id_token&scope=openid%20profile%20user_access&response_mode=form_post&nonce=637373859487758409.MzhhYTAoeiudtMTdlNS00NzgxLWJjMTQtNzM1YWE3NsdlkelasdNGYxMmQtMjZmYS00YmI2LTgwY2UtNDEwMTNhMWNkN2Zi&state=CfDJ8KCu3Hr4UOhLjOspjLNEh0VtJd4GNXqwdibjSiZf7FpUJOL0EDlFso0g0s_iOZHDNbP2aiHVfdzqJSmHkesd-bMjP6ThYva6AfZBa8UZcnGcwgo2ldlg4Fx9vmNVDuSlvHyTlHkd8yNndslkgoyHtfM4RMXamq1wny1J39BZRRATn1RdAsgaLgKP_QkxLaDCwgvdzjp3dKls5UVQE1j7MD6bcKR__1-VmfVKhROn1coQh7OJrea6Jni4jdV7e0wv70TVprGtseJFg8fyHg3KKW14xeX2orlkgls5aLe1uG0c5ehlapFXBirBSgFU3uqOWw0_iLeJUbTL8-HPooixynQRWe1WoiLnQuFYUu7Lx-usdlglvM4WvLfAyTZ5uQY_KsOtr08MxWRlQ5HHVk8Moe1k_N_3BCz8sdkgowwZEKsGiKd_iwcXgzxmgg&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=5.3.0.0
How can I fix this? It seems like my api is redirecting the client request to the microsoft login, but I'm thinking the way this should work is that my api should validate the token in the request or the scopes and grant access without redirecting the request to a login.
The key point here is to separate SPA OAuth entirely from API OAuth, with the following behaviours:
SPA does the redirect handling and gets a token from the Authorization Server - this code is Javascript based and does not need a C# 'web back end'
API validates tokens via the token signing public key of the Authorization Server - this code is C# based, which is a good language for API development
Authorization Server is an out of the box component such as Azure AD, and you never code issuing of JWTs yourself (good to see you doing this already)
Many online articles I've seen mix up these concerns and can be very confusing to people who are new to OAuth technologies. So it's important to be clear about what you want from the end solution.
API OAUTH CODE
Your C# code should only need a few lines by default, starting by adding a package such as this one:
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.8" />
The code to configure the API's security would then look something like this. Note that there is nothing about OpenIdConnect / web behaviour here.
private void ConfigureOAuthTokenValidation(IServiceCollection services)
{
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => {
options.Authority = "https://login.microsoftonline.com/mytenantid";
options.Audience = "api://default";
});
REAL WORLD SAMPLE
My .Net Core API solution has some stuff that may be of interest. These samples are quite advanced but enable you to run a local API and SPA that work together.
.Net Core Blog Post
API Code
API Security Configuration Code
SPA Code
The goal is to get the best end result for both API and SPA, so perhaps the above links will help you think about what that is in your case.

How can I secure simple HTML files using Azure AD?

I have a legacy static website that is just plain HTML and simple JavaScript for UI effects. There is no server side code, api, config files or anything in this website - just raw HTML files, CSS, pictures, etc.
The website will not be hosted in Azure. It will be on a local IIS server. If I pull the web site into Visual Studio, the "Configure Azure AD Authentication" wizard shows:
An incompatible authentication configuration was found in this project
().
How can I secure simple HTML files using Azure AD?
The Visual Studio "Configure Azure AD Authentication" wizard is intended for ASP.Net Web Apps and Web APIs.
In your case, what you are building is considered a "Single Page Application" or SPA. Even though you might have multiple pages, this term also applies to client side only web apps with no backend code.
For this, you should follow the Azure AD Javascript Single Page Application sample.
The gist of it is that you should us ADAL.js like shown in this sample's app.js, along the lines of:
// Configure ADAL
window.config = {
instance: 'https://login.microsoftonline.com/',
tenant: '[Enter your tenant here, e.g. contoso.onmicrosoft.com]',
clientId: '[Enter your client_id here, e.g. g075edef-0efa-453b-997b-de1337c29185]',
postLogoutRedirectUri: window.location.origin,
cacheLocation: 'localStorage', // enable this for IE, as sessionStorage does not work for localhost.
};
var authContext = new AuthenticationContext(config);
// Check For & Handle Redirect From AAD After Login
var isCallback = authContext.isCallback(window.location.hash);
authContext.handleWindowCallback();
$errorMessage.html(authContext.getLoginError());
if (isCallback && !authContext.getLoginError()) {
window.location = authContext._getItem(authContext.CONSTANTS.STORAGE.LOGIN_REQUEST);
}
// Check Login Status, Update UI
var user = authContext.getCachedUser();
if (user) {
//Do UI for authenticated user
} else {
//Show UI for unauthenticated user
}
// Register NavBar Click Handlers
$signOutButton.click(function () {
authContext.logOut();
});
$signInButton.click(function () {
authContext.login();
});
Note: There's also a Angular SPA sample.
The solution posted by Saca pointed me in the right direction, but adding the JS to every page was not a valid solution for me. There were thousands of HTML files, lots with no common JS file I could tack that ADAL code into. I would have had to find a way to insert that JS on all those pages.
My first solution was simply creating a normal .NET MVC app with the proper auth configured. Then I simply loaded this legacy content via an iFrame. This worked but was limiting for the users.
As Fei Xue mentioned in another comment, the next solution involved scrapping the iFrame but routing all requests for static files through a controller. Using this as a reference for understanding that: https://weblogs.asp.net/jongalloway/asp-net-mvc-routing-intercepting-file-requests-like-index-html-and-what-it-teaches-about-how-routing-works
The above solutions worked. However, eventually this app ended up as an Azure App Service and I simply turned on authentication at the app service level with just the pure html files.

Azure active directory v2.0 query for Web API integration on a SharePoint site

We have a SharePoint publishing site with anonymous access hosted on the internet. As per out latest requirements, we need to implement user login (AzureAD, Microsoft personal and work accounts, and more).
https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-flows
As per the documentation here, we want to implement this using Web API to get the secure information from the database. We are thinking about using MSAL.js file for user login and logout on the SharePoint and after getting a bearer token we can call the Web API for the additional data from our database.
Standalone Web APIs restriction: “You can use the v2.0 endpoint to build a Web API that is secured with OAuth 2.0. However, that Web API can receive tokens only from an application that has the same Application ID. You cannot access a Web API from a client that has a different Application ID. The client won't be able to request or obtain permissions to your Web API.”
How can we create two applications with same application ID at App Registration Portal? Or should we use the same application ID at SharePoint and Web API’s end?
There is no need to register two application, you only need to one register application. After you register the application, you can using the MSAL library below to get the token to call the web API:
<script class="pre">
var userAgentApplication = new Msal.UserAgentApplication("e5e5f2d3-4f6a-461d-b515-efd11d50c338", null, function (errorDes, token, error, tokenType) {
// this callback is called after loginRedirect OR acquireTokenRedirect (not used for loginPopup/aquireTokenPopup)
})
userAgentApplication.loginPopup(["user.read"]).then(function (token) {
var user = userAgentApplication.getUser();
console.log(token);
// signin successful
}, function (error) {
// handle error
});
</script>
And to protect the web API, you can use the same app and refer the code below:
public void ConfigureAuth(IAppBuilder app)
{
var tvps = new TokenValidationParameters
{
// The web app and the service are sharing the same clientId
ValidAudience = "e5e5f2d3-4f6a-461d-b515-efd11d50c338",
ValidateIssuer = false,
};
// NOTE: The usual WindowsAzureActiveDirectoryBearerAuthenticaitonMiddleware uses a
// metadata endpoint which is not supported by the v2.0 endpoint. Instead, this
// OpenIdConenctCachingSecurityTokenProvider can be used to fetch & use the OpenIdConnect
// metadata document.
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
AccessTokenFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider("https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration")),
});
}

Consume Secure Azure API from SharePoint online

I have developed Azure API app with authentication on feature, log in with Azure Active Directory, I need to consume this API from SharePoint online , I
I need to authenticate and consume the azure API, no signin-prompt, every thing should be handled in the script
need to use ADAL.js to authenticate secure API ,I cannot find any good reference about the JavaScript code, I was wondering if anyone have a good reference how the JavaScript code should look like?
Thanks!
Here are the steps to call the azure hosted API from SharePoint online using JavaScript and ADAL.js library, no signing-prompt, everything should be handled in the script using ADAL.js to authenticate secure API
Create and Configure API
Create azure API  
Publish your azure API in azure
Browse to azure portal, select your API application , select Authentication/Authorizations
Set the App Service Authentication: On
Action to take when request us not authenticated: Log in with Azure dictionary
Authentication providers: Express
Now the API is protected with Azure AD, if you navigate your API via browser you will be prompted for login
When we set authentication in Express mode, the app will be created automatically in Azure Active directory, you can see the name under Azure AD app
Navigate to Azure management portal, click on active directory in left navigation,
Click on the Directory which will be federated to your office 365 (or any source you want to call azure API which uses the same azure active directory as your configured for you API authentication)
Click on the Application, and you will find you AD app in the list which has been created with Express method as we discussed om step nr.3
Now we need to create new app in AAD which will be our communication channel from office 365 to Azure API, Click on ADD on footer
Enter name and select “WEB APPLICATION AND/OR WEB AP” option
For Sign in URL enter your SharePoint online Url which you are planning to call Azure API from
For APP ID URL, enter unique Url, this will be used as a unique logical identifier for your app.
After the app created, click on Configure, copy the Client ID which will be used later­­
Under Permission to other applications, Click “Add Application”, on the next page select “All Apps” and select you Azure API app which you double checked in step nr.8, and confirm
You will be redirected back to Configure page, Under Permission to other applications, now you see your azure API app is listed here, click on delegated permission, select the access to app
At the bottom of the page, click Manage manifest > Download manifest.
Download the file to a location where you can edit it.
In the downloaded manifest file, search for the oauth2AllowImplicitFlow property. Change the value of this property from false to true, and then save the file.
Click Manage manifest > Upload manifest, and upload the file that you updated in the preceding step.
Azure management portal, select setting and copy your AAD subscription ID for related AAD
Call Azure API from SharePoint Online
Once the above steps have been done, you can call azure API from sharePoint online which is using same Active directory above
Edit the page and add Script Editor web part
Add following script
· subscriptionId, see step nr.20
· clientId see step nr.13
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://secure.aadcdn.microsoftonline-p.com/lib/1.0.13/js/adal.min.js"></script>
<script type="text/javascript">
function CallAzureAPI() {
"use strict";
var subscriptionId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
var clientId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
window.config = {
subscriptionId: subscriptionId,
clientId: clientId,
postLogoutRedirectUri: window.location.origin,
endpoints: {
AzureApiUri: 'https://xxxxxxxxxxxx.azurewebsites.net'
},
cacheLocation: 'localStorage'
};
var authContext = new AuthenticationContext(config);
var isCallback = authContext.isCallback(window.location.hash);
authContext.handleWindowCallback();
if (isCallback && !authContext.getLoginError()) {
window.location = authContext._getItem(authContext.CONSTANTS.STORAGE.LOGIN_REQUEST);
}
// If not logged in force login
var user = authContext.getCachedUser();
if (user) {
// Logged in already
console.log(user);
}
else {
authContext.login();
}
// Acquire token for Files resource.
authContext.acquireToken(config.endpoints. AzureApiUri, function (error, token) {
// Handle ADAL Errors.
if (error || !token) {
console.log('ADAL error occurred: ' + error);
return;
}
var ApiUri = "https://xxxxxxxxx.azurewebsites.net/api/Get";
$.ajax({
type: "GET",
url: ApiUri,
headers: {
'Authorization': 'Bearer ' + token,
}
}).done(function (response) {
console.log('Successfully called API.');
console.log(response);
}).fail(function () {
console.log('Calling API failed.');
});
});
}
</script>
<input type='button' value='Call Azure API' onclick=" CallAzureAPI ();"/>
This solution works though after some time ( later I found out when AAD cookie is expired ) we get this error "Token renewal operation failed due to timeout ",
I did some research and I found out he getCachedUser or getUser methods look into the browser storage for id_token and returns a non-null user if there is a token inside the cache. It does not look into the token expiration time though.
What's happening here is since localStorage is used, tokens are preserved in the cache when one re-opens the browser (and hence getCachedUser returns a non-null object) but the AAD cookie is expired (unless user checked the keep me signed in checkbox when logging in). Since the cookie is expired, acquire token call fails with the "login required" error.
so as workaround I checked , keep me signed in checkbox when logging in and it works .
It is possible to call the web API which protected by Azure AD from the SharePoint online using the JavaScript, however it is very complex.
Here are the steps for your reference:
Developing the web API which protected by Azure AD
register an native app on the same tenant
enable the implicit flow for the native app
grant the native app to access the web API from Azure portal
using the admin_consent for the native app to grant permission fo the organization
developing a web page in web API project for the redrect page for the OAuth 2.0 request
write the code in the web page using windows.postMessage to post the token to the parent page
$().ready(function () {
if (window.parent != null) {
// get the token from URL here
var token = "123";
console.log(window.location);
window.parent.postMessage(token, "*");
}
})
In SharePoint online page, using iframe to send the implict flow like code below
<iframe id="iframe_id" src="https://login.microsoftonline.com/{tenanit}.onmicrosoft.com/oauth2/authorize?response_type=token&client_id={clientId}&resource={webApiAppIdUri}&redirect_uri={redirect_uri}&prompt=none"></iframe>
<script>
var token = "";
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event) {
token=event.data;
console.log(event.data);
}
</script>
Here is a figure to help understanding the progress:

Azure AD OpenIDConnect + ASP.NET Core - Authenticate and Extra Permissions/Token?

I am using the following bits against my Azure AD to authenticate with ASP.NET Core.
https://azure.microsoft.com/en-us/resources/samples/active-directory-dotnet-webapp-openidconnect-aspnetcore/
https://github.com/Azure-Samples/active-directory-dotnet-webapp-openidconnect-aspnetcore
I have the basic login/auth working after creating an Azure AD app. User can login/logout.
My question is given this, what's the best way when a user Auth's to log to a DB? I thought about making the redirect URL to an endpoint, saving, then just redirecting back to "Home" but is that ideal?
Also, is it possible to retrieve a bearer token via this approach? Or does this require another type of call or extending "scope"? So that for example I could retrieve the authenticated users Manager.
https://graph.microsoft.com/v1.0/me/manager
My question is given this, what's the best way when a user Auth's to log to a DB? I thought about making the redirect URL to an endpoint, saving, then just redirecting back to "Home" but is that ideal?
This way only able to log those who already sign-in your app successfully. It is not able to log those users who are attempt to sign-in your app but enter the wrong password.
Azure AD already provide lots of report to gain visibility into the integrity and security of your organization’s directory.( refer here)
And if you are using the Azure AD Premium, you can review the sign-in activities via the Azure new portal below:
And if you want to store the sign-in activity in your web app, you can write the custom code after the token is verified. Here is the code for your reference:
// Configure the OWIN pipeline to use OpenID Connect auth.
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
ClientId = Configuration["AzureAD:ClientId"],
Authority = String.Format(Configuration["AzureAd:AadInstance"], Configuration["AzureAd:Tenant"]),
ResponseType = OpenIdConnectResponseType.IdToken,
PostLogoutRedirectUri = Configuration["AzureAd:PostLogoutRedirectUri"],
Events = new OpenIdConnectEvents
{
OnRemoteFailure = OnAuthenticationFailed,
OnTokenValidated = context => {
//write the custom code to store users login-in
return Task.FromResult(0); }
},
});
Also, is it possible to retrieve a bearer token via this approach?
Yes. We can get the token after receive the authorization code. You can refer the code sample here to acquire the token from asp.net core app.

Resources