I set up my Startup Class below:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddMicrosoftIdentityWebApiAuthentication(Configuration);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
My AzureAd appsettings.json is below:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "2f0d9252-e207-4d7f-b4da-*******",
"TenantId": "de1ef02c-7cfa-46b8-a02b-*******",
"Audience": "2f0d9252-e207-4d7f-b4da-*******"
}
Now my controller below:
[HttpGet]
public async Task<string> Get()
{
//To be put on appsettings.json
string clientId = "2f0d9252-e207-4d7f-b4da-0cc618e77c93";
string tenantId = "de1ef02c-7cfa-46b8-a02b-61ab78bc602b";
var app = PublicClientApplicationBuilder.Create(clientId)
.WithRedirectUri("http://localhost:5000")
.WithTenantId(tenantId)
.Build();
string[] scopes = new string[] { };
//Azure Login Success here
var result = await app.AcquireTokenInteractive(scopes).ExecuteAsync();
//Azure service graph get success.
string graphResult = await "https://graph.microsoft.com/beta/me"
.WithOAuthBearerToken(result.AccessToken)
.GetStringAsync();
//I pass the AccessToken from the result. But I'm getting UnAuthorized.
string authorizeResult = await "https://localhost:44328/weatherforecast/AuthorizeGet"
.WithOAuthBearerToken(result.AccessToken)
.GetStringAsync();
return graphResult;
}
[Authorize]
[HttpGet]
public async Task<string> AuthorizeGet()
{
return "Authorize";
}
The Azure Login will success here. But after I get the token and request to AuthorizeGet Api. It will give me Unauthorized 401.
Anything I missed out for the configuration?
Below is my Directory Authentication config.
Related
I’m trying to use Microsoft.Identity.Web to call an API from my .NET 6 Blazor server app. I can successfully login to authorized pages in my Blazor app but when I try to make an API call using CallWebApiForUserAsync I get an inner exception of “No account or login hint was passed to the AcquireTokenSilent call”.
The couple of posts here on Stack Overflow and the MS documentation look to me like I have it configured correctly. What am I missing?
appsettings.json:
"AzureAd": {
"Authority": "https://hivercom.b2clogin.com/XXXXXX.onmicrosoft.com/B2C_1_SignUpSignIn",
"Instance": "https://XXXXXX.b2clogin.com",
"TenantId": "XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX",
"ClientId": "XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX",
"CallbackPath": "/signin-oidc",
"Domain": "XXXXXX.onmicrosoft.com",
"SignedOutCallbackPath": "/signout/B2C_1_susi",
"SignUpSignInPolicyId": "B2C_1_SignUpSignIn",
"ResetPasswordPolicyId": "B2C_1_PasswordReset",
"EditProfilePolicyId": "B2C_1_EditProfile",
"ClientSecret": "XXXXXXXXXXXXXXXXXX"
},
"HiverAPI": {
"BaseUrl": "https://localhost:7075",
"Scopes": "https://XXXXXX.onmicrosoft.com/XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX/all"
},
Program.cs
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;
using BlazorB2C.Data;
using Microsoft.Identity.Web.TokenCacheProviders.InMemory;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Authorization;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient();
builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
.EnableTokenAcquisitionToCallDownstreamApi(new string[] { "https://XXXXXX.onmicrosoft.com/XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX/all" })
.AddDownstreamWebApi("HiverService", builder.Configuration.GetSection("HiverAPI"))
.AddInMemoryTokenCaches();
builder.Services.AddAuthorization(options =>
{
options.FallbackPolicy = options.DefaultPolicy;
});
builder.Services.AddRazorPages().AddMvcOptions(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();
builder.Services.AddServerSideBlazor()
.AddMicrosoftIdentityConsentHandler();
builder.Services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.Events.OnSignedOutCallbackRedirect = context =>
{
context.HttpContext.Response.Redirect(context.Options.SignedOutRedirectUri);
context.HandleResponse();
return Task.CompletedTask;
};
});
builder.Services.AddSingleton<WeatherForecastService>();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
Level.razor:
#page "/level"
#using Microsoft.Identity.Web
#inject IHttpClientFactory HttpClientFactory
#inject Microsoft.Identity.Web.ITokenAcquisition TokenAcquisitionService
#inject Microsoft.Identity.Web.IDownstreamWebApi DownstreamWebApi
#inject MicrosoftIdentityConsentAndConditionalAccessHandler ConsentHandler
#attribute [Authorize]
#attribute [AuthorizeForScopes(ScopeKeySection = "HiverAPI:Scopes")]
<PageTitle>Level</PageTitle>
Log out
<h3>Level</h3>
#code {
private const string ServiceName = "HiverService";
protected override async Task OnInitializedAsync()
{
try
{
error >> string token = await TokenAcquisitionService.GetAccessTokenForUserAsync(new string[] { "https://XXXXXX.onmicrosoft.com/XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX/all" });
error >> var value = await DownstreamWebApi.CallWebApiForUserAsync(
ServiceName,
options =>
{
options.RelativePath = $"HiverUser";
});
}
catch (Exception ex)
{
var message = ex.Message;
ConsentHandler.HandleException(ex);
}
try
{
error >> await DownstreamWebApi.CallWebApiForUserAsync(
ServiceName,
options =>
{
options.HttpMethod = HttpMethod.Put;
options.RelativePath = $"HiverUser/0fd30e94-a116-4ef0-b222-4125546b8561";
});
}
catch (Exception ex)
{
var message = ex.Message;
ConsentHandler.HandleException(ex);
}
}
}
Azure configuration for client app:
Azure configuration for API
CallWebApiForAppAsync uses the on-behalf flow, which is not available for Azure AD B2C.
https://github.com/AzureAD/microsoft-identity-web/wiki/b2c-limitations
You cannot use ITokenAcquisition.GetTokenForAppAsync or IDownstreamApi.CallWebApiForAppAsync in Azure AD B2C web apps.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/access-tokens
As an alternative, you can request access tokens for downstream APIs(Hiver.API in your case) using GetAccessTokenForUserAsync. The resulting access token can be used to call the API. You'll need to use a standard HttpClient instead of IDownstreamWebApi. Full example can be found here:
https://github.com/Azure-Samples/ms-identity-blazor-server/blob/main/WebApp-your-API/B2C/README-Incremental.md
Relevant code from Github:
private async Task PrepareAuthenticatedClient()
{
var accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(new[] { _TodoListScope });
Debug.WriteLine($"access token-{accessToken}");
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
What I'm trying to do is after I login my azure credentials in MSAL login pop up, I will use the token to access authorize API.
I set up my Startup.cs below:
I use Microsoft.Identity.Web nuget package.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddMicrosoftIdentityWebApiAuthentication(Configuration);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
My apsettings.json AzureAd is configured below:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "2f0d9252-e207-4d7f-b4da-********",
"TenantId": "de1ef02c-7cfa-46b8-a02b-********",
"Audience": "2f0d9252-e207-4d7f-b4da-********"
}
In my controller I have 2 API.
[HttpGet]
public async Task<string> LoginAzure()
{
//To be put on appsettings.json
string clientId = "2f0d9252-e207-4d7f-b4da-********";
string tenantId = "de1ef02c-7cfa-46b8-a02b-********";
var app = PublicClientApplicationBuilder.Create(clientId)
.WithRedirectUri("http://localhost:5000")
.WithTenantId(tenantId)
.Build();
string[] scopes = new string[] { };
//Azure Login Success here
var result = await app.AcquireTokenInteractive(scopes).ExecuteAsync();
//I pass the AccessToken from the result. But I'm getting UnAuthorized.
string authorizeResult = await "https://localhost:44328/weatherforecast/AuthorizeGet"
.WithOAuthBearerToken(result.AccessToken)
.GetStringAsync();
return authorizeResult;
}
[Authorize]
[HttpGet]
public async Task<string> AuthorizeGet()
{
return "Authorize";
}
The LoginAzure is for calling the MSAL.
And the AuthorizeToken is an authorize api.
I can successfully get the access token after login in azure using MSAL. But if I use the access token to the Bearer Token and access the authorize API, I'm getting 401 Unauthorize.
Anything I missed out on my configuration? Or how do I set up properly my MSAL?
If I try to access the microsoft graph api with the access token, I can successfuly access it.
I want to host my REST API on Azure Service App with authorization bearer token.
On Azure portal I've done:
Create new App Service (testservice).
On Authentication/Authorization:
App Service Authentication -> On
Action to take when request is not authenticated -> Log in with Azure Active Directory. Set Authentication Providers to Active Directory Authentication with Management mode: Express and create new Azure AD Application (register new app: testserviceapp)
Token Store -> On
On App registrations -> testserviceapp:
Authentication -> Web -> Redirect URIs: https://testservice.azurewebsites.net/.auth/login/aad/callback
Authentication -> Web -> Implicit grant -> Access tokens: true, ID tokens: true
Authentication -> Supported account types -> Accounts in this organizational directory only (... - Single tenant)
Authentication -> Advanced settings -> Treat application as a public client: No
Certificates & secrets -> create new ClientSecret
Expose an API -> Add a scope: https://testservice.azurewebsites.net/user_impersonation
Expose an API -> Add a scope: https://testservice.azurewebsites.net/WeatherForecast
Next, I created RestApiService project in VisualStudio and publish into Azure.
appsettings.json in my RestApiService host:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"AzureAd": {
"ClientId": "xxx",
"Domain": "xxx.onmicrosoft.com",
"Instance": "https://login.microsoftonline.com/",
"TenantId": "xxx",
"CallbackPath": "/signin-oidc",
"ClientSecret": "xxx",
"AppIDURL": "https://testservice.azurewebsites.net",
"ConfigView": "API"
}
}
WeatherForecastController.cs:
[ApiController]
[Route("[controller]")]
[Authorize]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
Startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(AzureADB2CDefaults.BearerAuthenticationScheme).AddAzureAdBearer(options => Configuration.Bind("AzureAd", options));
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
On client app I successfully get AccessToken:
static AuthToken GetToken()
{
var uri = "https://login.microsoftonline.com/";
var method = $"{TenantID}/oauth2/token";
var client = new RestClient(uri);
var request = new RestRequest($"{uri}{method}", Method.POST);
request.AddParameter("grant_type", "client_credentials");
request.AddParameter("client_id", ClientID);
request.AddParameter("client_secret", ClientSecret);
request.AddParameter("resource", "https://testservice.azurewebsites.net/");
return client.Execute<AuthToken>(request).Data;
}
But when I try to call api with this token, I receive 401 Unauthorized error (You do not have permission to view this directory or page.):
static void GetItems(string token)
{
var client = new RestClient("https://testservice.azurewebsites.net/");
var request = new RestRequest("https://testservice.azurewebsites.net/WeatherForecast", Method.GET);
request.AddHeader("Authorization", $"bearer {token}");
var response = client.Execute(request);
}
What am I doing wrong?
Net core Web API project. I registered app in azure AD for Web API app. I configured swagger and I registered one more app in Azure AD.
I am trying to do authorization on my webapis based on groups. In appsettings.json I have all the values.
Below is my startup looks like.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services
.AddAuthentication(o =>
{
o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.Authority = azureActiveDirectoryOptions.Authority;
o.TokenValidationParameters = new TokenValidationParameters
{
ValidAudiences = new List<string>
{
azureActiveDirectoryOptions.AppIdUri,
azureActiveDirectoryOptions.ClientId
},
ValidateIssuer = true
};
});
services.AddScoped<IAuthorizationHandler, GroupsCheckHandler>();
services.AddAuthorization(options =>
{
options.AddPolicy("GroupsCheck", policy =>
{
policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
policy.RequireAuthenticatedUser();
policy.Requirements.Add(new GroupsCheckRequirement("2a39995a-8fd1-410e-99e2-11cf6046090d"));
});
});
services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" });
c.AddSecurityDefinition("oauth2", new OAuth2Scheme
{
Type = "oauth2",
Flow = "implicit",
AuthorizationUrl = swaggerUIOptions.AuthorizationUrl,
TokenUrl = swaggerUIOptions.TokenUrl
});
c.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>>
{
{ "oauth2", new[] { "readAccess", "writeAccess" } }
});
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.OAuthClientId(swaggerUIOptions.ClientId);
c.OAuthClientSecret(swaggerUIOptions.ClientSecret);
c.OAuthRealm(azureActiveDirectoryOptions.ClientId);
c.OAuthAppName("Swagger");
c.OAuthAdditionalQueryStringParams(new { resource = azureActiveDirectoryOptions.ClientId });
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
app.UseAuthentication();
app.UseMvc();
}
When I run the application using https://localhost:44319/swagger
Now I have Authorize button in my swagger. Whenever I try to Authorize, It will ask me to enter user name and password. Authentication works as expected. Next I want to hit /api/values/users/{id}. the controller looks below.
[Authorize(Policy = "GroupsCheck")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
}
I need group based authorization. In startup I have added policy.
services.AddAuthorization(options =>
{
options.AddPolicy("GroupsCheck", policy =>
{
policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
policy.RequireAuthenticatedUser();
policy.Requirements.Add(new GroupsCheckRequirement("2a39995a-8fd1-410e-99e2-11cf6046090d"));
});
});
Below is my GroupsCheckHandler.cs
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
GroupsCheckRequirement requirement)
{
GraphServiceClient client = await MicrosoftGraphClient.GetGraphServiceClient();
//Tried to get user and dint work for me
var user = await client.Me.Request().GetAsync();
//Here exception occurs
var groupList = await client.Groups.Request().GetAsync();
var result = false;
foreach (var group in groupList)
{
if (requirement.groups.Equals(group.Id))
{
result = true;
}
}
if (result)
{
context.Succeed(requirement);
}
}
Below is my MicrosoftGraphClient.cs
public static async Task<GraphServiceClient> GetGraphServiceClient()
{
// Get Access Token and Microsoft Graph Client using access token and microsoft graph v1.0 endpoint
var delegateAuthProvider = await GetAuthProvider();
// Initializing the GraphServiceClient
graphClient = new GraphServiceClient(graphAPIEndpoint, delegateAuthProvider);
return graphClient;
}
private static async Task<IAuthenticationProvider> GetAuthProvider()
{
AuthenticationContext authenticationContext = new AuthenticationContext(authority);
ClientCredential clientCred = new ClientCredential(clientId, clientSecret);
// ADAL includes an in memory cache, so this call will only send a message to the server if the cached token is expired.
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenAsync(graphResource, clientCred).ConfigureAwait(false);
var token = authenticationResult.AccessToken;
var delegateAuthProvider = new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token.ToString());
return Task.FromResult(0);
});
return delegateAuthProvider;
}
Now Whenever I start hitting to my api am getting exception in groupshandler.cs
Microsoft.Graph.ServiceException: Code: Authorization_RequestDenied
Message: Insufficient privileges to complete the operation.
I have added Microsoft graph permission to my app in azure AD. I thing to read groups we need Admin consent. Here I am struggling. Below permission I can see under enterprise application in azure ad under user consent tab.
Below is token format generated through authenticationContext.AcquireTokenAsync method
Other side this token also looks strange to me and this is missing many fields.
Now someone please help me what wrong steps I have done in the above implementation. Can someone help in this regard. Any help would be very helpful for me. Thanks a lot
You were using client credential to get the access Token. So you need to add application permissions(not delegated permissions) on Azure portal.
After adding the application permissions, you also need to grant admin consent.
I'm making an application with asp core 2.1 and Vue. My problem is the login to the production server (Windows Server 2012 R2). When I want to do login throws the following error.
Failed to load resource: the server responded with a status of 500 (Internal Server Error)
Access to XMLHttpRequest at 'http://{ip...}/api/account/login' from origin 'http://localhost:8000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Uncaught (in promise) TypeError: Cannot set property 'error' of undefined at eval (Login.vue?03db:289)
All other endpoints respond except the login.
However, when I test in the development backend pointing to the production DB, it logs without problems and all the endpoints also respond (get, post etc). As far as I can understand it can be a configuration issue, maybe on the server related to IIS. As I understand if it were a matter of CORS no endpoint would work
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("defaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Password.RequiredLength = 5;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
options.TokenValidationParameters = new TokenValidationParameters
{
...
});
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddCors();
//Autorizacion a recursos de la aplicación
services.AddMvc().AddJsonOptions(ConfigureJson);
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
});
}
private void ConfigureJson(MvcJsonOptions obj) {
obj.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ApplicationDbContext context)
{
app.UseAuthentication();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseCors(builder => builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
);
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseHttpsRedirection();
app.UseMvc();
}
Program.cs
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
}