Plz, help in my trouble.
I created new application in MS VS 2017. Type ASP.NET Core 2 MVC
I can't find solution for my problem with authorize.
I added to Startup
public void ConfigureServices(IServiceCollection services) {
services.AddScoped<USDb, USDb>();
services.Scan(scan => scan.FromAssemblyOf<USDb>()
.AddClasses()
.AsImplementedInterfaces());
InjectionContainer = services.BuildServiceProvider();
services.AddMvc();
//Auth
services.AddAuthentication(o => {
o.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, o => {
o.LoginPath = new PathString("/Account/Login");
o.ReturnUrlParameter = "RedirectUrl";
o.AccessDeniedPath = new PathString("/Account/AccessDenied");
o.LogoutPath = new PathString("/Account/Logout");
o.ExpireTimeSpan = TimeSpan.FromDays(1);
o.SlidingExpiration = true;
o.Cookie.SameSite = SameSiteMode.Strict;
o.Cookie.Name = ".USAUTH";
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
} else {
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMvc(routes => {
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
app.UseAuthentication();
}
My AccountController method
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginModel model, string redirectUrl = null) {
if (ModelState.IsValid) {
await Authenticate("login");
return Redirect(redirectUrl);
}
return View();
}
private async Task Authenticate(string userName) {
var claims = new List{ new Claim(ClaimsIdentity.DefaultNameClaimType, userName) };
ClaimsIdentity id = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(id));
}
When I try to go Home/Index application redirect me to account - it's very good. But after I entered login data and submited it to login method application redirect me to login page again... and again.... etc
But cookies appeared, but application not created HttpContext.User.Identity and still redirecting me to login page....
And I don't know what todo(((( please help(( I lose the hope(
You must call app.UseAuthentication(); before app.UseMvc(...);.
Banal error but I had the same problem a while ago after upgrading to asp.net core 2.0.
Related
When I successfully signed in to the HTTP context and I'm redirected to the home controller, the Authorize Attribute of the HomeController redirects me to the login path, because I don't know. (Infinite loop)
The validate async method doesn't sign me out or reject the cookie (I checked it.)
But where's the problem?
I have the same authentication process in an ASP.NET Core 2.1 project and there it works perfectly.
I test it with a custom authorize attribute to check the context. In the context I've a principal and I'm authenticated.
But why redirect me the standard authorize attribute?
My configuration is:
HomeController.cs
public class HomeController : Controller
{
[Authorize]
public IActionResult Index()
{
return this.View("Index");
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/auth/login/";
options.ExpireTimeSpan = TimeSpan.FromDays(7);
options.Events.OnValidatePrincipal = ValidateAsync;
});
services.AddControllersWithViews();
services.AddAntiforgery();
services.AddDbContext<ApplicationDbContext>((serviceProvider, options) =>
{
options.UseSqlServer(this.Configuration.GetConnectionString("DefaultConnection"));
options.EnableSensitiveDataLogging();
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if(env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
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.UseAuthorization();
app.UseAuthentication();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
public static async Task ValidateAsync(CookieValidatePrincipalContext context)
{
context = context ?? throw new ArgumentNullException(nameof(context));
String userId = context.Principal.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.NameIdentifier)?.Value;
if(userId == null)
{
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return;
}
ApplicationDbContext dbContext = context.HttpContext.RequestServices.GetRequiredService<ApplicationDbContext>();
User user = await dbContext.Users.FindAsync(Guid.Parse(userId));
if(user == null)
{
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return;
}
if(!user.StaySignedIn &&
user.LastLogin != null &&
(user.LastLogin.Subtract(TimeSpan.FromDays(1)) > DateTimeOffset.Now))
{
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return;
}
}
AuthController.cs
[Route("/login")]
[Route("/auth/login")]
public async Task<IActionResult> Login([FromForm]LoginModel loginModel)
{
Claim nameIdentifier = new Claim(ClaimTypes.NameIdentifier, user.Id.ToString());
ClaimsIdentity userIdentity = new ClaimsIdentity(new List<Claim> { nameIdentifier }, CookieAuthenticationDefaults.AuthenticationScheme);
ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(userIdentity);
await this.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal);
return this.RedirectToAction("Index", "Home");
}
I found the solution for my problem.
The order of
app.UseAuthorization();
app.UseAuthentication();
Must be changed and then it works.
The issue closed When using identity is not allowed to use the cookies.
It overrides some of the information.
Git Discussion
Cookie options tell the authentication middleware how the cookie works in the browser.In the Startup class, add this code in your ConfigureServices Method
services.AddAuthentication(options =>
{
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie(options => { options.LoginPath = "/Login"; });
Whenever i hit Login the user is Signed in using signinManager.PasswordSignInAsync and result.Succeeded is true. The problem is that if i Call Login second time the user does not presist. User.Identity.IsAuthenticated should be true next time but it is always false
[HttpGet("[action]")]
public async Task<IActionResult> Login()
{
try
{
if (User.Identity.IsAuthenticated)
{
Console.WriteLine("You are alredy Logged In...................");
var claims = User.Claims;
return Ok("Authenticated");
}
else
{
var result = await signinManager.PasswordSignInAsync("myEmail.com", "Password", true, true);
if (result.Succeeded)
{
Console.WriteLine("Logged in successfully....");
}
return Ok("Logged in successfully ");
}
}
catch (System.Exception e)
{
Console.WriteLine("........................................" +e.Message);
return Ok(e.Message);
throw;
}
}
ConfigureServices in StartUp.Cs looks like
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DbContextBase>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<AppUser, IdentityRole>()
.AddEntityFrameworkStores<DbContextBase>()
.AddDefaultTokenProviders();
services.AddMvc();
}
and Configure Method in Startup.cs looks like:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
{
HotModuleReplacement = true
});
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
}
The Login Method must return "Authenticated" when hit second time.
You forgot to configure the actual authentication method, such as cookies.
Use something like this in your ConfigureServices:
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
options.LoginPath = "/Identity/Account/Login";
options.AccessDeniedPath = "/Identity/Account/AccessDenied";
options.SlidingExpiration = true;
});
As title says, I want to implement some kind of authorization in .NET Core 2.1, but I have a trouble following their tutorials. I have set up the Startup.cs file
public void ConfigureServices(IServiceCollection services)
{
string connection = Configuration.GetConnectionString("DefaultConnection");
if (_currentEnvironment.IsDevelopment())
services.AddDbContext<PagesContext>(options => options.UseSqlServer(connection), ServiceLifetime.Singleton);
else
services.AddDbContext<PagesContext>(options => options.UseMySql(connection), ServiceLifetime.Singleton);
services.AddIdentity<User, IdentityRole>(opts=>
{
opts.Password.RequireDigit = false;
opts.Password.RequiredLength = 8;
opts.Password.RequireLowercase = false;
opts.Password.RequireNonAlphanumeric = false;
opts.Password.RequireUppercase = false;
opts.User.AllowedUserNameCharacters = null;
}).AddEntityFrameworkStores<PagesContext>()
.AddDefaultTokenProviders();
services.AddSingleton<IStringLocalizerFactory, EFStringLocalizerFactory>();
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
var supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("en-GB"),
new CultureInfo("en"),
new CultureInfo("ru-RU"),
new CultureInfo("ru"),
new CultureInfo("uk-UA"),
new CultureInfo("ua")
};
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("ru-RU"),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
});
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(
routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
}
);
And I have the HomeController.cs file with following contents:
[HttpGet]
public async Task<IActionResult> Index()
{
var user = await _userManager.GetUserAsync(HttpContext.User);
if (user != null)
{
if (await _userManager.IsInRoleAsync(user, "Admin"))
{
return LocalRedirectPermanent("/Admin/Index");
}
else if (await _userManager.IsInRoleAsync(user, "Carrier"))
{
return LocalRedirectPermanent("/Carrier/Index");
}
return LocalRedirectPermanent("/Passenger/Index");
}
Page indexPage = db.Page.Include(u => u.Title).Include(u => u.Description).FirstOrDefault(p => p.Tag == "main_page");
return View();
}
As it can be seen from the code I want to detect, if user is logged in - to show the corresponding page. If not - page, which enables login procedure. But the default route is ignored, when authentication is used. If I launch this from Visual Studio, it navigates me to /Account/AccessDenied?ReturnUrl=%2FPassenger%2FIndex , not /Home/Index. How can I override this?
I found it. And it seems, it it not the fault of .NET Core. It based on headers.
In my case, LocalRedirectPermanent("/Admin/Index") was called several times, therefore, it automatically called "/Admin/Index", bypassing "/Home/Index". I think, it is due to this. Therefore, I have fixed in my browser(Firefox) this way: Reset the history(forms, cookies, etc) and perform in Visual Studio Build->Rebuild Solution. And it started working again. Hope, this helped someone
I am developing an application using ASP.Net MVC core 2.1 where I am continuously getting the following exception.
"InvalidOperationException: No authentication handler is registered for the scheme 'CookieSettings'. The registered schemes are: Identity.Application, Identity.External, Identity.TwoFactorRememberMe, Identity.TwoFactorUserId. Did you forget to call AddAuthentication().AddSomeAuthHandler?"
I went through articles did the necessary changes but exception remains the same. I am unable to figure out what to do next .
following is my startup.cs :
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.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<Infrastructure.Data.ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<Infrastructure.Data.ApplicationDbContext>();
services.AddSingleton(Configuration);
//Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
//Add services
services.AddTransient<IContactManagement, ContactManagement>();
services.AddTransient<IRepository<Contact>, Repository<Contact>>();
services.AddTransient<IJobManagement, JobJobManagement>();
services.AddTransient<IRepository<Job>, Repository<Job>>();
services.AddTransient<IUnitManagement, UnitManagement>();
services.AddTransient<IRepository<Sensor>, Repository<Sensor>>();
services.AddTransient<IJobContactManagement, JobContactManagement>();
services.AddTransient<IRepository<JobContact>, Repository<JobContact>>();
services.AddTransient<IJobContactEventManagement, JobContactEventManagement>();
services.AddTransient<IRepository<JobContactEvent>, Repository<JobContactEvent>>();
services.AddTransient<IJobsensorManagement, JobsensorManagement>();
services.AddTransient<IRepository<JobSensor>, Repository<JobSensor>>();
services.AddTransient<IEventTypesManagement, EventTypesManagement>();
services.AddTransient<IRepository<EventType>, Repository<EventType>>();
services.AddTransient<IJobSensorLocationPlannedManagement, JobSensorLocationPlannedManagement>();
services.AddTransient<IRepository<JobSensorLocationPlanned>, Repository<JobSensorLocationPlanned>>();
services.AddTransient<IDataTypeManagement, DataTypeManagement>();
services.AddTransient<IRepository<DataType>, Repository<DataType>>();
services.AddTransient<IThresholdManegement, ThresholdManegement>();
services.AddTransient<IRepository<Threshold>, Repository<Threshold>>();
services.AddTransient<ISeverityTypeManagement, SeverityTypeManagement>();
services.AddTransient<IRepository<SeverityType>, Repository<SeverityType>>();
services.AddTransient<ISensorDataManagement, SensorDataManagement>();
services.AddTransient<IRepository<SensorData>, Repository<SensorData>>();
services.AddTransient<ISensorLocationManagement, SensorLocationManagement>();
services.AddTransient<IRepository<SensorLocation>, Repository<SensorLocation>>();
services.AddTransient<ISensorStatusTypeManagement, SensorStatusTypeManagement>();
services.AddTransient<IRepository<SensorStatusType>, Repository<SensorStatusType>>();
services.AddTransient<IRepository<SensorDataToday>, Repository<SensorDataToday>>();
services.AddTransient<ISensorDataTodayManagement, SensorDataTodayManagement>();
services.AddSession();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(20);
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Account/Login/";
options.LogoutPath = "/Account/Logout/";
});
services.AddAuthentication(options =>
{
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// 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();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
login/signin.cs:
public async Task<IActionResult> Login(LoginViewModel model, ApplicationUser applicationUser, string returnUrl = "/RealTimeChart/Index")
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
ApplicationUser signedUser = await _userManager.FindByEmailAsync(model.Email);
if (signedUser != null)
{
var result = await _signInManager.PasswordSignInAsync(signedUser.UserName, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
//Authorize login user
if (!string.IsNullOrEmpty(signedUser.UserName))
{
await AuthorizeUser(signedUser);
}
_logger.LogInformation(1, "User logged in.");
HttpContext.Session.SetString("UserName", model.Email);
int AccountTypeId = _contactManagement.GetContactDetailsByUserId(signedUser.Id).UserAccountTypeId;
HttpContext.Session.SetString("AccountTypeId", AccountTypeId.ToString());
return RedirectToLocal(returnUrl);
}
else
{
if (_userManager.SupportsUserLockout && await _userManager.GetLockoutEnabledAsync(signedUser))
{
await _userManager.AccessFailedAsync(signedUser);
}
}
if (result.RequiresTwoFactor)
{
return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
}
if (signedUser.LockoutEnd != null)
{
_logger.LogWarning(2, "User account locked out.");
return View("Lockout");
}
else
{
ViewBag.InvalidPassword = "Password is Invalid";
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
}
else
{
ViewBag.InvalidEmail = "Email is Invalid";
return View(model);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
i tried changing my login method to below but no success, your help is highly appreciated
List<Claim> userClaims = new List<Claim>
{
new Claim(applicationUser.Id, Convert.ToString(applicationUser.Id)),
new Claim(ClaimTypes.Name, applicationUser.UserName),
new Claim(ClaimTypes.Role, Convert.ToString(applicationUser.Id))
};
var identity = new ClaimsIdentity(userClaims, CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,new ClaimsPrincipal(identity));
You should add the following instructions in the ConsigureServices method in class Startup.cs. You can add this at the beginning of the method:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(o =>
{
o.Cookie.Name = options.CookieName;
o.Cookie.Domain = options.CookieDomain;
o.SlidingExpiration = true;
o.ExpireTimeSpan = options.CookieLifetime;
o.TicketDataFormat = ticketFormat;
o.CookieManager = new CustomChunkingCookieManager();
});
This code let you to register the authentication handler for cookies authentication.
I suggest you this read this or this
Apparently, if you use services.AddIdentityCore(), you should use
signInManager.CheckPasswordSignInAsync() as opposed to signInManager.PasswordSignInAsync() because signInManager.PasswordSignInAsync() can only be used with cookies.
I try to secure up my API with a cookie token.
Everything is working fine, i try to sign in i generate a cookie the cookie is set by browser, and then i try to request /auth/info2. The cookie is send but i got an 401 error.
Can u give me a hint? How to solve this problem?
Currently my code looks like that:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
{
//options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))
options.UseInMemoryDatabase("som_tmp");
}
);
services.AddTransient<IEmailSender, EmailSender>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddIdentity<SomUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(o =>
{
o.Cookie = new CookieBuilder()
{
HttpOnly = false,
Name = "som_session"
};
});
services.ConfigureApplicationCookie(options =>
{
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
});
services.AddAuthorization();
services.AddMvc();
services.AddOData();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ApplicationDbContext context, UserManager<SomUser> userManager, RoleManager<IdentityRole> roleManager)
{
var model = GetEdmModel(app.ApplicationServices);
app.UseDefaultFiles();
app.UseStaticFiles(new StaticFileOptions
{
ServeUnknownFileTypes = true
});
app.UseAuthentication();
app.UseMvc(routebuilder =>
{
routebuilder.Count().Filter().OrderBy().Expand().Select().MaxTop(null);
routebuilder.MapODataServiceRoute("oData", "oData", model);
});
DbInitializer.Initialize(context, userManager, roleManager);
}
Controller:
[Authorize]
[HttpGet("info2")]
public async Task<JsonResult> Get2()
{
return Json("Info2");
//return Json( await GetCurrentUser() );
}
[AllowAnonymous]
[HttpPost("login2")]
public async Task<JsonResult> Login2([FromBody] LoginDto loginDto)
{
var user = await _userManager.FindByNameAsync(loginDto.Username);
if (user == null)
{
user = await _userManager.FindByEmailAsync(loginDto.Username);
}
if (user != null)
{
var passwordHasher = new PasswordHasher<SomUser>();
if (passwordHasher.VerifyHashedPassword(user, user.PasswordHash, loginDto.Password) == PasswordVerificationResult.Success)
{
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity));
return Json(true);
}
}
return Json(false);
}
i got it working with setting the DefaultScheme:
services.AddAuthentication(o =>
{
o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
You will receive 401 atleast once since there a redirection to login involved.
second result should have 'true' as a output.