I’m working on a microservice architecture with the following projects: WEB, API, and AUTH. I’m encountering a problem in the WEB project where I need to retrieve the authentication token from the AUTH service.
Web project Configuration file:
Code:
using IdentityModel.Client;
using Mango.Web;
using Mango.Web.Services;
using Mango.Web.Services.Interfaces;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.CodeAnalysis.Options;
using Microsoft.Extensions.Options;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddHttpClient<IProductService, ProductService>();
ServiceUrls.ProductAPIBase = builder.Configuration["ServiceUrls:ProductAPI"];
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddControllersWithViews();
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(10);
options.LoginPath = "/Account/Login";
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = builder.Configuration["ServiceUrls:IdentityAPI"];
options.ClientId = "mango";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("mango");
options.SaveTokens = true;
options.CallbackPath = "/signin-oidc";
options.SignedOutCallbackPath = "/signout-callback-oidc";
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
};
options.Events = new OpenIdConnectEvents
{
OnTokenResponseReceived = async context =>
{
var tokens = context.TokenEndpointResponse;
if (tokens != null)
{
// Установка куки с токеном доступа
context.HttpContext.Response.Cookies.Append("access_token", tokens.AccessToken, new CookieOptions
{
HttpOnly = true,
Secure = true,
SameSite = SameSiteMode.None,
Expires = DateTimeOffset.UtcNow.AddMinutes(10)
});
}
}
};
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/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.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
Identity project configuration file
using Mango.Services.Identity;
using Mango.Services.Identity.Entities;
using Mango.Services.Identity.Initializer;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
});
builder.Services.AddIdentity<ApplicationUser, IdentityRole>( options =>
{
options.SignIn.RequireConfirmedAccount = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
builder.Services.AddScoped<IDbInitializer, DbInitializer>();
builder.Services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
options.EmitStaticAudienceClaim = true;
}).AddInMemoryIdentityResources(Claims.IdentityResource)
.AddInMemoryApiScopes(Claims.ApiScopes)
.AddInMemoryClients(Claims.Clients)
.AddAspNetIdentity<ApplicationUser>()
.AddDeveloperSigningCredential(); ;
builder.Services.AddScoped<ApplicationUser>();
builder.Services.AddRazorPages();
var initializer = builder.Services.BuildServiceProvider().GetService<IDbInitializer>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/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();
initializer.Initialize();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.MapRazorPages();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
Identity project PageModel that must return me token:
public async Task<IActionResult> OnPost()
{
// check if we are in the context of an authorization request
var context = await _interaction.GetAuthorizationContextAsync(Input.ReturnUrl);
// the user clicked the "cancel" button
if (Input.Button != "login")
{
if (context != null)
{
// This "can't happen", because if the ReturnUrl was null, then the context would be null
ArgumentNullException.ThrowIfNull(Input.ReturnUrl, nameof(Input.ReturnUrl));
// if the user cancels, send a result back into IdentityServer as if they
// denied the consent (even if this client does not require consent).
// this will send back an access denied OIDC error response to the client.
await _interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied);
// we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
if (context.IsNativeClient())
{
// The client is native, so this change in how to
// return the response is for better UX for the end user.
return this.LoadingPage(Input.ReturnUrl);
}
return Redirect(Input.ReturnUrl ?? "~/");
}
else
{
// since we don't have a valid context, then we just go back to the home page
return Redirect("~/");
}
}
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(
Input.Username, Input.Password, Input.RememberLogin, lockoutOnFailure: false);
if (result.Succeeded)
{
var user = await _users.FindByNameAsync(Input.Username);
await _events.RaiseAsync(
new UserLoginSuccessEvent(user.UserName,
user.Id, user.FirstName + " " + user.LastName,
clientId: context?.Client.ClientId));
if (context != null)
{
return Challenge(new AuthenticationProperties { RedirectUri = "/" }, "oidc");
}
if (Url.IsLocalUrl(Input.ReturnUrl))
{
return Redirect(Input.ReturnUrl);
}
else if (string.IsNullOrEmpty(Input.ReturnUrl))
{
return Redirect("~/");
}
else
{
throw new Exception("Invalid return Url");
}
}
const string error = "invalid credentials";
await _events.RaiseAsync(new UserLoginFailureEvent(Input.Username, error, clientId:context?.Client.ClientId));
ModelState.AddModelError(string.Empty, LoginOptions.InvalidCredentialsErrorMessage);
}
// something went wrong, show form with error
await BuildModelAsync(Input.ReturnUrl);
return Page();
}
And Error text:
InvalidOperationException: No authentication handler is registered for the scheme ‘oidc’. The registered schemes are: Identity.Application, Identity.External, Identity.TwoFactorRememberMe, Identity.TwoFactorUserId, idsrv, idsrv.external. Did you forget to call AddAuthentication().AddSomeAuthHandler?
Can somebody help me with it?
I need to implement normal authorization inside web project.
Vlad Zaremsky is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.