MatBlazor Button Link is not parsed by routing - url-routing

I'm using MatBlazor for my website and I've implemented Google Authentication using this great blog:
Google Authentication in Server-Side Blazor
I want a login button (MatButton) in my MatAppBar.
The original code has a link: <a class="ml-md-auto btn btn-primary" href="/Login" target="_top">Login</a>.
This link works. I'm redirected to my OnGetAsync of my LoginModel. But it is not matching my UI style.
This button does go to the right page, but my OnGetAsync of my LoginModel is not triggered and only the default Sorry, there's nothing at this address. is shown.
<MatButton Class="mat" Outlined="true" Icon="Google" Label="Inloggen" Link="/Login"></MatButton>
I think I need to tweak my routing, but can't find how.
Update:
My Login.cshtml.cs:
[AllowAnonymous]
public class LoginModel : PageModel
{
public IActionResult OnGetAsync(string returnUrl = null)
{
string provider = "Google";
// Request a redirect to the external login provider.
var authenticationProperties = new AuthenticationProperties
{
RedirectUri = Url.Page("./Login",
pageHandler: "Callback",
values: new { returnUrl }),
};
return new ChallengeResult(provider, authenticationProperties);
}
}
My Startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
app.UseEmbeddedBlazorContent(typeof(MatBlazor.BaseMatComponent).Assembly);
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}

I had a very similar issue and used the navigationmanager option listed above but had to pass the ForceLoad parameter as "true"
void LoginClick()
{
navigationManager.NavigateTo("/login",true);
}

Use the full URL
<MatButton Class="mat" Outlined="true" Icon="Google" Label="Inloggen"
Link="BaseUrl/Login"></MatButton>
or
using navigation manager to navigate that page
#inject NavigationManager navigationmanager
<MatButton Class="mat" Outlined="true" Icon="Google" Label="Inloggen" Onclick="#(()=>)"></MatButton>
code{
void clic()
{
navigationmanager.Navigateto("/Login")
}
}

Related

Unable to consume .NET Core web API in React JS After Single Sign on using Azure Ad

I have successfully implemented Single Sign on with Azure Ad and fetched profile using MS Graph API but when I try to consume my dot net web API it is showing me error Unauthorized(401)
May be I am missing something in configuration,
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "example.onmicrosoft.com",
"ClientId": "392xxxx2-bxx4-4xxf-axxc-505bd9c6d8b4",
"TenantId": "06xxx2xbe-9xxe-4xx8-bxxd-e1a6ebxxxxd",
"scopes": "api://3xxxxe52-bxx4-4xxf-axx2c-505bxxxxb4/User.Read"
}
here is my Startup.cs code
using AutoMapper;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using TMS.API.Configuration;
using TMS.DAL.Configuration;
using TMS.DAL.Mapper;
using Microsoft.Identity.Web;
namespace TMS.API
{
public class Startup
{
public Startup(IConfiguration configuration) => Configuration = configuration;
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddMicrosoftIdentityWebApi(Configuration, "AzureAd");
services.AddControllers();
services.AddSwaggerGen(s =>
{
s.SwaggerDoc("v1", new OpenApiInfo() { Title = "TMS API", Version = "V1" });
});
services.AddAutoMapper(typeof(Startup));
services.Configure<Setting>(Configuration.GetSection("Settings"));
services.RegisterEngineServices();
services.RegisterRepositories();
}
// 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.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.WithHeaders()
.WithExposedHeaders());
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "TMS v1");
});
}
private static void AddAutoMapper(IServiceCollection services)
{
var mapperConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new BSRMapperClass());
});
IMapper mapper = mapperConfig.CreateMapper();
services.AddSingleton(mapper);
}
}
}
Controller:
[Route("api/[controller]")]
[ApiController]
[RequiredScope(RequiredScopesConfigurationKey ="AzureAd:scopes")]
public class GeneralController : ControllerBase
{
private readonly IConfiguration _configuration;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IGeneralService _generalService;
public GeneralController(IGeneralService generalService, IConfiguration configuration, IHttpContextAccessor httpContextAccessor)
{
_generalService = generalService;
_configuration = configuration;
_httpContextAccessor = httpContextAccessor;
}
[Authorize(Roles ="Admin")]
[Route("[action]")]
[HttpGet]
public async Task<DataTransfer<IEnumerable<MonthResponseModel>>> GetMonthList()
{
return await _generalService.GetMonthList();
}
React config file:
export const msalConfig = {
auth: {
clientId: "ddxxxx8-xxf-4xxd-bxx2-a4xxxxxd6c",
authority: "https://login.microsoftonline.com/061f82be-9xxe-4xx8-bdad-e1xxxxxb6d", // This is a URL (e.g. https://login.microsoftonline.com/{your tenant ID})
redirectUri: "http://localhost:3001",
},
cache: {
cacheLocation: "sessionStorage", // This configures where your cache will be stored
storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
}
};
export const apiConfig = {
uri: "https://example.azurewebsites.net/api", // e.g. http://localhost:5000/api
scopes: ["api://3xxxx2-bxx4-4xxf-a72c-505xxxxx8b4/User.Read"] // e.g. ["scp1", "scp2"]
};
// Add scopes here for ID token to be used at Microsoft identity platform endpoints.
export const loginRequest = {
scopes: ["User.Read"]
};
// Add the endpoints here for Microsoft Graph API services you'd like to use.
export const graphConfig = {
graphMeEndpoint: "https://graph.microsoft.com/v1.0/me"
};
Here we have:
Client(React) App Essentials
Server(.net web API) App Essentials
I have exposed my API and added scope and authorized client Application:
Exposed API
I need help as I have been stuck in this issue from a couple of days and Kindly do let me know where to add Users in Client App or in Server app?
Issue Fixed!
In appsetting I changed "scopes": "api://3xxx52-bxx4-40xf-axxc-505xxxx8b4/User.Read" to "scopes": "User.Read"and reorder the middleware in Configure Method (Startup file):
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Loreal TMS v1");
});
app.UseHttpsRedirection();
//app.UseStaticFiles();
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
);
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}

.NET 5.0 + React + Google Auth

I am struggling to build a secure React web app with Net 5.0 backend (all in the same project) and would like some advice.
All within the same Visual Studio project.
Eg:
Project
ClientApp (React)
Controllers (C# endpoints eg /api/data, api/filtereddata)
Program.cs
Startup.cs
(Implementation #1)
I can make the front-end login using the 'react-google-login' npm package. That works well but it doesn't protect the endpoints in the controllers (/api/data).
<div>
<GoogleLogin
clientId={clientId}
buttonText="Login with Google"
onSuccess={onSuccess}
onFailure={onFailure}
cookiePolicy={'single_host_origin'}
style={{ marginTop: '100px' }}
isSignedIn={true}
/>
</div>
I have also discovered I can verify that google token on the server by using something akin to:
const onSuccess = (res) => {
const tokenBlob = { tokenId: res.tokenId };
axios.post(`/api/auth/google`, tokenBlob)
.then(res => {
})
.catch(function (error) {
console.log("axois error", error);
})
};
[Route("/api/[controller]")]
public class AuthController : Controller
{
[AllowAnonymous]
[HttpPost("google")]
public IActionResult Google([FromBody] GoogleToken t)
{
var payload = GoogleJsonWebSignature.ValidateAsync(t.tokenId, new GoogleJsonWebSignature.ValidationSettings()).Result;
}
}
But it seems awkward to do this for every API call that the UI makes.
(Implementation #2)
I have tried doing this all in the backend (Microsoft.AspNetCore.Authentication.Google), that sort of worked with AddGoogle & AddCookie, that would re-direct to a Google login page when I tried to get data from the backend (via [Authorize]) - but I could not get React to notice that it was logged in/out.
// ConfigureServices
services.AddCors(options =>
{
options.AddDefaultPolicy(builder => {
builder.WithOrigins("https://localhost","https://accounts.google.com")
.AllowAnyHeader().AllowAnyMethod();
});
});
services.AddControllers().AddNewtonsoftJson();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(o =>
{
o.LoginPath = "/signin";
o.LogoutPath = "/signout"; // ??
o.ExpireTimeSpan = TimeSpan.FromHours(1);
})
.AddGoogle(options =>
{
options.ClientId = "";
options.ClientSecret = "";
options.SaveTokens = true;
options.CallbackPath = "/signin-google";
});
// Configure
app.UseCors();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints => {
endpoints.MapControllers();
});
app.UseSpa(spa => {
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment()) {
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
// Home controller
public class HomeController : Controller {
[Route("/signin")]
public IActionResult SignIn() {
var authProperties = new AuthenticationProperties { RedirectUri = "/"
};
return new ChallengeResult(GoogleDefaults.AuthenticationScheme, authProperties);
}
[Authorize]
[Route("/signout")]
[HttpPost]
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync();
return Ok("Logged out");
}
}
// Api controller
[Authorize]
[Route("api/[controller]")]
public class DataController : Controller
{
private readonly ILogger<DataController> _logger;
public MatchListController(ILogger<DataController> logger)
{
_logger = logger;
}
[HttpGet]
public ResponseViewModel Get([AllowNull] DateTime? d) => new ResponseViewModel(matchDate ?? DateTime.UtcNow)?.Result;
}
The second implementation will re-direct to Google and require the login but React doesn't know the page is logged in. So how can it get the logged info to display the username etc?
So what is the best practice? Am I close? I feel close!
What I'd love to see would be an example of the WeatherForecast React template in visual studio with a working Google login that uses [Authorize] on the API data controller.
Any suggestions welcome.
Edit: Added some code

Azure Internal Server Error on ASP.NET Core Web API Controller

I've created a ASP.NET Core Web API Controller with React Js App and it on Azure. After sever try I am able to upload on Azure and now I'm getting error on my API. When I click on Customer it does not give me error but there is no data from SQL Database.
Can someone guide me on How to connect DB to my ASP.NET Core Web API or suggest me where am I doing wrong?
I tried post/add data to customer table but I am getting Internal server Error
Here is sql connection string in my appsetting.json
"ConnectionStrings": {
"DevConnection": "jdbc:sqlserver://aspapireact.database.windows.net:1433;database=ReactTask;user=*****;password=*****;encrypt=true;trustServerCertificate=false;hostNameInCertificate=*.database.windows.net;loginTimeout=30;"}
The Startup.cs
namespace RahulTask1
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddCors(options =>
{
options.AddDefaultPolicy(builder =>
{
builder.WithOrigins("https://aspapireact.azurewebsites.net")
.AllowAnyMethod()
.AllowAnyHeader();
});
});
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
services.AddDbContext<DatabaseContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DevConnection")));
}
// 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();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
}
}
And this the API I am trying to call
https://aspapireact.azurewebsites.net/api/Customers
You can see my code on GitHub
https://github.com/rlbrs/ASPAPIReact
In this project you will see the local server connection string but I've updated with above one and same with appserver.json
you can use the configuration builder to the configure services method. To build the key value pair from the appsettings.json on any environment based appsettings file, add following code to the ConfigureServices method (This is not mandatory)
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false , reloadOnChange : true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
And then you read the value for the connection string as
Configuration["ConnectionStrings:DevConnection"]
PS: Any particular reason you are using jdbc connection? why not use the standard dot net based connection string?

Accessing claims from ProfileService

I have a simple project with identityserver4 and ElmahCore.
I added a custom IProfileService and IResourceOwnerPasswordValidator
Authentication works like a charm, but custom claims I add in the Profile Service do not show up in the principal when I try to restrict Elmah access.
services.AddElmah<SqlErrorLog>(
options => {
options.CheckPermissionAction = context => context.User.Identity.IsAuthenticated;
}
);
The User Identity exists and is authenticated, but the only claims that exist are sub, name, auth_time, idp and amr.
Other custom claims do not show up.
The value of sub is set to the value I expect it to have.
I also added this line before calling services.AddIdentityServer(), but nothing changed:
services.AddScoped<IUserClaimsPrincipalFactory<HaproUser>, AppClaimsPrincipalFactory>();
The application configuration is set up like this:
public void Configure(IApplicationBuilder app)
{
app.UseForwardedHeaders(
new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedProto
}
);
if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseFileServer();
app.UseIdentityServer();
app.UseMvcWithDefaultRoute();
app.UseElmah();
}
And service configurations is this:
services.AddMvc();
services.AddScoped<IUserClaimsPrincipalFactory<HaproUser>, AppClaimsPrincipalFactory>();
var builder = services.AddIdentityServer()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApis())
.AddInMemoryClients(Config.GetClients());
builder.Services.AddSingleton<IUserRepository, UserRepository>();
builder.AddProfileService<HaproProfileService>();
builder.AddResourceOwnerValidator<HaproPasswordValidator>();
if (Environment.IsDevelopment())
{
builder.AddDeveloperSigningCredential();
}
The ProfileService is fairly straight forward:
public class HaproProfileService : IProfileService
{
// IsActiveAsync omitted
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var sub = context.Subject.GetSubjectId();
var claims = context.RequestedClaimTypes.Select(type => MapClaim(type, user)).ToList();
context.AddRequestedClaims(claims);
return Task.FromResult(0);
}
private static Claim MapClaim(string type, HaproUser user)
{
switch (type)
{
case "name":
return new Claim(type, user.DisplayName);
// Omitted cases here
}
}
}

Protect IdentityServer4 Api with itself

We are currently working on a identityserver4 implementation which will also have a few api calls.
Those api calls should only be available if a user is authorized(with the bearer token).
In the Startup.cs we have the services.AddIdentityServer() since this is the identityServer, and also added the AddAuthentication() call to make sure the authorized endpoints are only available for authorized connections.
Startup.cs => ConfigureServices():
services.AddIdentityServer();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:9000";
options.RequireHttpsMetadata = false;
options.ApiName = "identityserver4";
});
Startup.cs => Configure():
app.UseAuthentication();
app.UseIdentityServer();
//app.UseAuthentication();
using the UseAuthentication() before or after UseIdentityServer() does not change anything.
My api call within the identityserver is still avaialble to all.
Currently using postman to test the calls.
Do i need to add something? Is there something i missed?
Kind regards,
Walter
edit 1: added controller and full startup.cs
UserController.cs:
namespace Identity.Controllers
{
[Authorize]
[Route("[controller]")]
public class UserController : ControllerBase
{
private readonly ILogger _logger;
private readonly IUserBusinessLogic _userBusinessLogic;
public UserController(ILogger<UserController> logger, IUserBusinessLogic userBusinessLogic)
: base()
{
_logger = logger;
_userBusinessLogic = userBusinessLogic;
}
[Route("")]
[HttpGet]
public async Task<ActionResult<IList<UserDto>>> GetAllUsers()
{
var users = await _userBusinessLogic.GetAll();
return users.ToList();
}
}
}
Startup.cs:
namespace Identity
{
public class Startup
{
private readonly IConfiguration _configuration;
private readonly ILogger _logger;
public Startup(IConfiguration configuration, ILogger<Startup> logger)
: base()
{
_configuration = configuration;
_logger = logger;
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore()
.AddJsonFormatters()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddRazorViewEngine();
services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = context => new ValidationProblemDetailsResult();
});
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:9000";
options.RequireHttpsMetadata = false;
options.ApiName = "identityserver4";
});
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddPersistedGrantStore<PersistedGrantStoreBusinessLogic>()
.AddResourceStore<ResourceBusinessLogic>()
.AddClientStore<ClientBusinessLogic>()
.AddProfileService<ProfileBusinessLogic>()
.AddCorsPolicyService<CorsPolicyBusinessLogic>();
services.AddCors(options =>
{
options.AddPolicy("default",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader().Build());
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseCors("default");
app.UseIdentityServer();
app.UseStaticFiles();
app.ConfigureExceptionHandler(_logger);
app.UseMvcWithDefaultRoute();
}
}
}
I just wrote some code for the exact same purpose, and I struggled with the same issues as you do.
According to the Identity Server Doc, do not forget to specify the authentication scheme in [Authorize] attribute.
Startup.cs:
services.AddAuthentication()
.AddIdentityServerAuthentication("Bearer", options =>
{
options.Authority = "http://localhost:9000";
options.RequireHttpsMetadata = false;
options.ApiName = "identityserver4";
});
Note that "Bearer" is given to AddIdentityServerAuthentication and not to AddAuthentication.
Controller.cs:
[Authorize(AuthenticationSchemes = "Bearer")]
public IActionResult YourControllerAction()
{
}
Hope it will works for you!
Found my problem!
in my startup i used services.AddMvcCore() when i should have used services.AddMvc() OR just add the services.AddAuthorization which is not added using services.AddMvcCore().
I came upon this solution after doing some research for something else. In my research i came upon this page: https://offering.solutions/blog/articles/2017/02/07/difference-between-addmvc-addmvcore/
It explains the differences between AddMvc() and AddMvcCore().
So after adding services.AddAuthorization() my problem was solved and the api within my identityserver where protected.
Thank you to all who tried to help me!
this answer may comes late but comes late better than never , using IdentityServer to secure other APIs and do not secure the main token or access provider may seems silly somehow ,so in this case if you want to secure the Api That implement IdentityServer it self you can add the predefined IdentityServer scope IdentityServerApi in the allowed scopes and also for the client scopes , and then you have to configure the services to use the local authentication (provided by identityserver) by adding services.AddLocalApiAuthentication();
and the final part is to add the authorize attribute to the controller or the action method as you wish as follow [Authorize(Policy = LocalApi.PolicyName)]
and in the end you can add claims policy authorization side by side with the local authentication

Resources