Bearer token 401 authorization error from Angular5 APP to .Net 4.7 back-end API - angular5

Working with Angular 5 on in our app, we are authenticating through the Azure Active Directory(AAD), getting a bearer token to access the backend API. We are not having any success in the athentication call to the backend api, getting a 401 unauthrized. Both App and Api is hosted in Azure. Any help would be appreciated in troubleshooting this issue:
The App service Registration on AAD:
Angular App (using the adal-angular5 1.0.36 library):
Application ID: aaaaa-aaaaa-aaaaa-aaaaa-aaaaa
Object ID: bbbbbb-bbbbb-bbbb-bbbbb-bbbbbb
App ID URI: https://frontendapp.azurewebsites.net
Home Page URL: https://homepage.frontendapp.com
Backend API (.Net 4.7):
Application ID: cccccc-cccccc-cccccc-cccccc-cccccc
Object ID: dddddd-ddddd-ddddd-dddddd-dddddd
App ID URI: https://backendapi.azurewebsites.net
Home Page URL: https://homepage.backendapi.com
App side
config for adal-angular5 on the Angular App:
config: adal.Config = {
tenant: 'common',
clientId: 'aaaaa-aaaaa-aaaaa-aaaaa-aaaaa',
postLogoutRedirectUri: window.location.origin,
endpoints: {
ApiUri: "https://homepage.backendapi.com",
}
};
Call to get the bearer token:
this.adal5Service.acquireToken("https://backendapi.azurewebsites.net")
call to the api
we verified the options contains the header which has the bearer token:
this.adal5HttpService.get('/api/hello', options);
On the API side
we are using the Microsoft Owin library (Microsoft.Owin 4.0.0 nuget package),
API startup:
public partial class Startup
{
/// <summary>
/// Initializes OWIN authentication.
/// </summary>
/// <param name="app">Web API AppBuilder</param>
public void Configuration(IAppBuilder app)
{
if (app == null)
{
throw new ArgumentNullException("app");
}
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = "common",
Audience = "https://backendapi.azurewebsites.net"
});
}
}
API side controler
[HttpGet]
[Authorize]
[Route("hello")]
public string GetHello()
{
try
{
var result = "we hit it yay!!!!!!!!!!!!!!!";
return result;
}
catch (Exception ex)
{
var msg = "not hit saddd.....";
var error = new Exception(msg, ex);
throw error;
}
}
With the [Authorize] attribute we are getting a 401 unauthorized, with no additional error message.
Without the [Authorize] attribute we are hitting the Controller method just fine and getting the return results.
I was able to confirm the bearer token that's sent from the App is indeed the bearer token we get in the header in our Api. I can't figure out why we are getting the unauthorized.
Is the config setting in correct on the APP or the API side?
Is there additional configuration needed in Azure?
Any help would be appreciate it!

Related

ASP.NET Core 6 Web API using Azure Authentication

I have an ASP.NET Core 6 Web API and a single page quasar (Vue.js) app.
I followed the msal-browser documentation to set up Azure AD App registration for both apps and was able to get the msal-browser demo app (an Angular SPA) and API authenticating.
Utilising the same Azure AD app registration I have been trying to sub in my own API and Vue js app.
I can login and get a token with my Vue js app. The token contains my AzAD details
I can access AllowAnonymous routes on my API from my Vue js App but I get a 401 UnAuthorized response when I try and use the AD token on my API.
A couple of things I suspect I might not be doing correctly
Not requesting with the scope properly
Not sending the request to the api properly
Not loading the config in the api properly
Edit
It looks like the problem is that I am not sending the request to the api properly.
I pointed the msal-browser demo Angular app at my api and it was able to retrieve the TodoList from the endpoint I replicated. Whereas my Vue app could only fetch the data when the endpoint had no [Authorize] attribute.
I have tried Swagger, Postman and my Vue.js app with the token copied from the Angular app and with the token acquired by my Vue app and get a 401 Authorization response with all. I have verified the tokens on jwt.ms.
The msal-browser doco for using the token only says to include the header Authorization: Bearer <token>.
I can't really see whats going on in the Angular client to prepare the request, but the Angular SPA demo app has access to more info, such as the app's Az clientId, authority: https://login.microsoftonline.com/(tenantId), redirectURi and scopes.
Any help in getting one of Swagger, Postman or My Vue app to use the Azure AD token against the api would be appreciated.
End Edit
Here is some of my code
msal-browser prop up
const msalConfig = {
auth: {
clientId: '<< removed >>',
authority: "https://login.microsoftonline.com/<< removed >>",
scopes: ['api://<< removed >>/access_as_user'],
redirectUri: "http://localhost:4200",
},
cache: {
cacheLocation: "sessionStorage",
storeAuthStateInCookie: false,
}
};
const msalInstance = new msal.PublicClientApplication(msalConfig);
const loginRequest = {
// scopes: ["openid", "profile", "User.Read"]
scopes: ["openid", "profile", "User.Read", 'api://<< removed >>/access_as_user']
};
function SignIn() {
msalInstance.loginPopup(loginRequest)
.then(loginResponse => {
state.signedIn = true;
state.signedInAs = loginResponse.account.name;
state.idToken = loginResponse.idToken;
state.accessToken = loginResponse.accessToken;
console.log(loginResponse);
}).catch(error => {
console.log(error);
});
}
Where I call the Api
async function FnTest(e) {
const config = {
headers: {
Authorization: 'Bearer << removed >>'
}
}
try {
// axios
const result = await api.get('/ToDos', config);
console.log(result)
} catch(ex) {
console.log(ex)
}
}
Program.cs from my Web API:
// not sure whether I am loading IConfiguration properly here
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration);
builder.Services.AddAuthorization();
var app = builder.Build();
...
Web API's appsettings.json:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "<<removed>>.onmicrosoft.com",
"TenantId": "<<removed>>",
"ClientId": "<<removed>>"
}
Any help would be greatly appreciated. I have been at this for some time now.
Also worth noting I guess that I have had auth working on this Web API when I create my own token. I have also tried the AzAd token in the API's swagger page and get the same result

Fetch data return Untheorized 401 access to asp.net core API protected in Azure AD

Im new to `webassembly blazor, Im spend too much time trying to figure out what's wrong here but I couldnt manage.
I have the following scenario:
Asp.net API registered and protected in Azure AD
Expose API with Scope AcessApi with status enabled
A Client application is added to authorized client applications
Token configuration both are checked Access Token and ID Token
And a client app that will call the API, developed in webassembly blazor
client app is registered in Azure AD
Client API permissions has delegated permission to use my client API
with correct scope AccessApi.
I tested the API using swagger interface, it forces user to authenticate first before accessing the API.
I tested using curl and grabbed the token from swagger interface and works perfectly fine.
curl -X GET "http://localhost:9400/api/getdata" -H "accept: text/plain" -H "Authorization: Bearer XX"
However, when my client application trying to access the API, a sign-in page pop-up for credentials, I could see the Token ID at browser bar being retrieved and while calling the API the app logs error not authorized
program class of the client application:
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
//builder.Logging.SetMinimumLevel(LogLevel.Debug);
////builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddScoped<CustomAuthorizationMessageHandler>();
builder.Services.AddHttpClient("AccessApi",
client => client.BaseAddress = new Uri("http://localhost:9400"))
.AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
.CreateClient("AccessApi"));
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add(scope);
});
await builder.Build().RunAsync();
}
in CustomAuthorizationMessageHandler class I have defined:
private static string scope = #"api://xxx-35fc2470889f/AccessApi";
public CustomAuthorizationMessageHandler(IAccessTokenProvider provider,
NavigationManager navigationManager)
: base(provider, navigationManager)
{
ConfigureHandler(
authorizedUrls: new[] { "http://localhost:9400" },
}
In appsettings.json a defined the client id of the API and tenant id without scopes since they are been defined in the CustomAuthorizationMessageHandlerclass:
{
"AzureAd": {
"Authority": "https://login.microsoftonline.com/<tenant_id>",
"ClientId": "<clientid>",
"CallbackPath": "/signin-oidc",
"ValidateAuthority": "true"
}
}
After a successful login via Azure AD, I call to fetch data from the API here
protected override async Task OnInitializedAsync()
{
...
try
{
responseBody = await Http.GetStringAsync("/api/getdata"); # use base URL of the API
}
catch (AccessTokenNotAvailableException ex)
{
ex.Redirect();
}
}
the console logs
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[1]
Authorization was successful.
info: System.Net.Http.HttpClient.AccessApi.ClientHandler[100]
Sending HTTP request GET http://localhost:9400/api/getdata
:9400/customer-manager/api/getdata:1 Failed to load resource: the server responded with a status of 401 (Unauthorized)
What could be wrong here?
Is there a way how to print the return token?
Update
I tested the API using Postman where auth Grant type is Implicit, after successful login, I store token on variable and passed in the header as Bearer the API return 401 Unauthroized. I decoded the token it contains the right scope AccessApi , with the correct clientId. what could be wrong here ?
If you want to call Microsoft graph and your custom API in one blazor webassembly project, we can implement it by creating different HTTP client to call different API
For example
Register a server API app
Register an AAD app for the Server API app
Expose an API
Register a client app
Register a client app
Enable Implicit grant flow
Add API permissions. ( API app permissions)
Configure API app
Please add the following code in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
services.AddCors(options =>
{
options.AddDefaultPolicy(
builder => builder.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod());
});
services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
.AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, options =>
{
options.Authority += "/v2.0";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuers = new[] {
$"https://sts.windows.net/{Configuration["AzureAD:TenantId"]}/",
$"https://login.microsoftonline.com/{Configuration["AzureAD:TenantId"]}/v2.0"
},
RoleClaimType = "roles",
// The web API accepts as audiences both the Client ID (options.Audience) and api://{ClientID}.
ValidAudiences = new[]
{
options.Audience,
$"api://{options.Audience}"
}
};
});
....
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.OAuthClientId(Configuration["Swagger:ClientId"]);
c.OAuthScopeSeparator(" ");
c.OAuthAppName("Protected Api");
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Configure Client APP
Create custom AuthorizationMessageHandler for Graph API and custom API
// custom API
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
{
public CustomAuthorizationMessageHandler(IAccessTokenProvider provider,
NavigationManager navigationManager)
: base(provider, navigationManager)
{
ConfigureHandler(
authorizedUrls: new[] { "<your web API url>" },
scopes: new[] { "the API app scope" });
}
}
Add the following code to the program.cs
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
builder.Services.AddScoped<CustomAuthorizationMessageHandler>();
// register HTTP client to call our own api
builder.Services.AddHttpClient("MyAPI", client => client.BaseAddress = new Uri("<your web API url>"))
.AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("<the API app scope>");
});
await builder.Build().RunAsync();
}
}
Call the api
#inject IHttpClientFactory _clientFactory
var httpClient = _clientFactory.CreateClient("<the client name you register>");
await apiClient.GetStringAsync("path");
Finally I found the issue was on the server side ASP.net core where I was validating the token in ConfigureServices at startup class:
// For token parameters validation
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(o =>
{
o.Audience = "<xx>"; // Application id
o.Authority = "https://login.microsoftonline.com/<xx>"; // Tenant ID
//Token validation
o.TokenValidationParameters = new TokenValidationParameters {ValidateIssuerSigningKey = false, ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = true};
});
I had to disable Issuer since the token is coming from a different application.

How do I acquire the right token for a Web API from within an Azure Function App or Javascript?

We have a web service which requires authentication before use. When you type in the URL of the Web Service directly in the browser, everything works fine. However, if you were to try and call this very same service from Javascript, it doesn't work because authentication has yet to happen.
I've tried calling getAccessTokenAsync (this is part of the OfficeJS libray) but ended up getting one of those 1300x errors. Also, since this call is still in Preview I would like to avoid it.
The code below gets invoked when you enter the URL to the webservice directly in the browser windows. You're authenticated and everything works fine.
I just don't know how to do the equivalent authentication from within an Azure Function App, or Javascript (from a Web-Add-In)
public partial class AuthStartup
{
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=316888
// This part is for web sso so web pages can consume the API without obtaining a token
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
// http://www.cloudidentity.com/blog/2014/11/17/skipping-the-home-realm-discovery-page-in-azure-ad/
Notifications = new WsFederationAuthenticationNotifications
{
RedirectToIdentityProvider = (context) =>
{
context.ProtocolMessage.Whr = "ourcompany.com";// similar effect to domain_hint from client so users never see the "choose account" prompt
return Task.FromResult(0);
}
},
MetadataAddress = ConfigurationManager.AppSettings["ida:MetadataAddress"],
Wtrealm = ConfigurationManager.AppSettings["ida:Audience"],
// this part is needed so that cookie and token auth can coexist
TokenValidationParameters = new TokenValidationParameters
{
ValidAudiences = new string[] { $"spn:{ConfigurationManager.AppSettings["ida:Audience"]}" }
}
});
// This part is for bearer token authentication
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
},
MetadataAddress = ConfigurationManager.AppSettings["ida:MetadataAddress"],
});
}
}

How to reach signup page of openiddict authorization server?

I have built opeiddict as separate web application as authorization server. I am stuck with small problem, that is how I can go to user registration page directly though a link from the client web application. Right now I can go to login page, as your sample example:
public ActionResult SignIn() {
// Instruct the OIDC client middleware to redirect the user agent to the identity provider.
// Note: the authenticationType parameter must match the value configured in Startup.cs
return new ChallengeResult(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties {
RedirectUri = "/"
});
}
Is there a way to go to authentication server Account/Register from client app?
It looks like you can set the url in the redirect. See the following snippet:
[AllowAnonymous]
public IActionResult SignIn()
{
return new ChallengeResult(
OpenIdConnectDefaults.AuthenticationScheme,
new AuthenticationProperties
{
IsPersistent = true,
RedirectUri = Url.Action("SignInCallback", "Account")
});
}
See the docs here: Initiating the authentication flow

Azure mobile apps Custom + Facebook authentication with Xamarin.Forms

I'm working on a Xamarin Forms mobile app with .NET backend. I followed this guide and successfully set up custom authentications with one change in Startup.cs:
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY"),
ValidAudiences = new[] { Identifiers.Environment.ApiUrl },
ValidIssuers = new[] { Identifiers.Environment.ApiUrl },
TokenHandler = config.GetAppServiceTokenHandler()
});
Without "if (string.IsNullOrEmpty(settings.HostName))". Otherwise I am always getting unauthorized for all requests after login.
Server project:
Auth controller
public class ClubrAuthController : ApiController
{
private readonly ClubrContext dbContext;
private readonly ILoggerService loggerService;
public ClubrAuthController(ILoggerService loggerService)
{
this.loggerService = loggerService;
dbContext = new ClubrContext();
}
public async Task<IHttpActionResult> Post(LoginRequest loginRequest)
{
var user = await dbContext.Users.FirstOrDefaultAsync(x => x.Email == loginRequest.username);
if (user == null)
{
user = await CreateUser(loginRequest);
}
var token = GetAuthenticationTokenForUser(user.Email);
return Ok(new
{
authenticationToken = token.RawData,
user = new { userId = loginRequest.username }
});
}
private JwtSecurityToken GetAuthenticationTokenForUser(string userEmail)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, userEmail)
};
var secretKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY");
var audience = Identifiers.Environment.ApiUrl;
var issuer = Identifiers.Environment.ApiUrl;
var token = AppServiceLoginHandler.CreateToken(
claims,
secretKey,
audience,
issuer,
TimeSpan.FromHours(24)
);
return token;
}
}
Startup.cs
ConfigureMobileAppAuth(app, config, container);
app.UseWebApi(config);
}
private void ConfigureMobileAppAuth(IAppBuilder app, HttpConfiguration config, IContainer container)
{
config.Routes.MapHttpRoute("ClubrAuth", ".auth/login/ClubrAuth", new { controller = "ClubrAuth" });
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY"),
ValidAudiences = new[] { Identifiers.Environment.ApiUrl },
ValidIssuers = new[] { Identifiers.Environment.ApiUrl },
TokenHandler = config.GetAppServiceTokenHandler()
});
}
Client project:
MobileServiceUser user = await MobileClient.LoginAsync(loginProvider, jtoken);
Additionally I configured Facebook provider in azure portal like described here.
But it works only when I comment out app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions(){...}); in Startup.cs.
What I am missing to make both types of authentication works at the same time?
Since you have App Service Authentication/Authorization enabled, that will already validate the token. It assumes things about your token structure, such as having the audience and issuer be the same as your app URL (as a default).
app.UseAppServiceAuthentication() will also validate the token, as it is meant for local development. So in your example, the token will be validated twice. Aside from the potential performance impact, this is generally fine. However, that means the tokens must pass validation on both layers, and I suspect that this is not the case, hence the error.
One way to check this is to inspect the tokens themselves. Set a breakpoint in your client app and grab the token you get from LoginAsync(), which will be part of that user object. Then head to a service like http://jwt.io to see what the token contents look like. I suspect that the Facebook token will have a different aud and iss claim than the Identifiers.Environment.ApiUrl you are configuring for app.UseAppServiceAuthentication(), while the custom token probably would match it since you're using that value in your first code snippet.
If that holds true, than you should be in a state where both tokens are failing. The Facebook token would pass the hosted validation but fail on the local middleware, while the custom token would fail the hosted validation but pass the local middleware.
The simplest solution here is to remove app.UseAppServiceAuthentication() when hosting in the cloud. You will also need to make sure that your call to CreateToken() uses the cloud-based URL as the audience and issuer.
For other folks that find this issue
The documentation for custom authentication can be found here.
A general overview of App Service Authentication / Authorization can be found here.
The code you reference is only for local deployments. For Azure deployments, you need to turn on App Service Authentication / Authorization - even if you don't configure an auth provider (which you wouldn't in the case of custom auth).
Check out Chapter 2 of my book - http://aka.ms/zumobook