In AspBoilerPlate - Unauthorized error when calling from Angular when Windows Authentication is On - windows-authentication

Have already raised this before and thought I have addressed it as per what suggested on THIS and THIS but seems not!
I am using ABP template (Angular and ASP .NET CORE Application) on Full .Net Framework. I simply want to use Windows Authentication to Authenticate user.
I added [Authorize] to the Authenticate in the TokenAuthController and have finally got the HttpContext.User.Identity.Name populated but only when I call the Authenticate from the Swagger (http://localhost:21021/swagger). But I am getting Unauthorized error when calling the method from Angular (login.service.ts):
POST http://localhost:21021/api/TokenAuth/Authenticate 401 (Unauthorized)
Here is the steps I have taken so far:
Changed launchSetting.json:
"iisSettings": {
"windowsAuthentication": true,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:21021/",
"sslPort": 0
}
},
Added ExternalAuthenticationSource:
public class WindowsAuthSource : DefaultExternalAuthenticationSource<Tenant, User>, ITransientDependency
{
public override string Name
{
get { return "Windows Authentication"; }
}
public override Task<bool> TryAuthenticateAsync(string userNameOrEmailAddress, string plainPassword, Tenant tenant)
{
return Task.FromResult(true);
}
}
Added it to CoreModule:
Configuration.Modules.Zero().UserManagement.ExternalAuthenticationSources.Add<WindowsAuthSource>();
4.Adjust AuthConfigurer:
services.AddAuthentication(opt => {
opt.DefaultScheme = IISDefaults.AuthenticationScheme;
opt.DefaultAuthenticateScheme = IISDefaults.AuthenticationScheme;
opt.DefaultChallengeScheme = IISDefaults.AuthenticationScheme;
});
Adjust StartUp.cs:
services.Configure<IISOptions>(iis =>
{
iis.AuthenticationDisplayName = "WINDOWS";
iis.AutomaticAuthentication = true;
});
Changed Authenticate method in the TokenAuthController:
public async Task<AuthenticateResultModel> Authenticate([FromBody]
AuthenticateModel model)
{
//var username = WindowsIdentity.GetCurrent().Name.Split('\\').Last();
var username = HttpContext.User.Identity.Name;
model.UserNameOrEmailAddress = username;
var loginResult = await GetLoginResultAsync(
model.UserNameOrEmailAddress,
model.Password,
null
);
var accessToken = CreateAccessToken(CreateJwtClaims(loginResult.Identity));
return new AuthenticateResultModel
{
AccessToken = accessToken,
EncryptedAccessToken = GetEncrpyedAccessToken(accessToken),
ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds,
UserId = loginResult.User.Id
};
}
Sending dummy username and password from login.service.ts:
authenticate(finallyCallback?: () => void): void {
finallyCallback = finallyCallback || (() => { });
//Dummy data
this.authenticateModel.userNameOrEmailAddress = "DummyUsername";
this.authenticateModel.password = "DummyPassword";
this._tokenAuthService
.authenticate(this.authenticateModel)
.finally(finallyCallback)
.subscribe((result: AuthenticateResultModel) => {
this.processAuthenticateResult(result);
});
}

Related

Invalid JWT bearer token on .net 6.0 web api

I am trying to implement security to my app and used Identity and JWT bearer token to authenticate but I always get the invalid token response in my swagger. Tried many solutions but I still get the same response.
[HttpPost("[action]")]
public async Task<IActionResult> Login(LoginBindingModel login)
{
IActionResult actionResult;
var user = await userManager.FindByEmailAsync(login.Email);
if (user == null)
{
actionResult = NotFound(new {errors = new[] {$"User with email {login.Email} is not found."}});
}
else if (await userManager.CheckPasswordAsync(user, login.Password))
{
if(!user.EmailConfirmed)
{
actionResult = BadRequest(new { errors = new[] { $"Email not confirmed." } });
}
else
{
var token = GenerateTokenAsync(user);
actionResult = Ok(token);
}
}
else
{
actionResult = BadRequest(new { errors = new[] { $"Password is not valid." } });
}
return actionResult;
}
private string GenerateTokenAsync(IdentityUser user)
{
IList<Claim> userClaims = new List<Claim>
{
new Claim("UserName", user.UserName),
new Claim("Email", user.Email)
};
var x = jwtOptions.SecurityKey;
return new JwtSecurityTokenHandler().WriteToken(new JwtSecurityToken(
claims: userClaims,
expires: DateTime.UtcNow.AddMonths(1),
signingCredentials: new SigningCredentials(jwtOptions.SecurityKey, SecurityAlgorithms.HmacSha256)));
}
Program.cs
using ASPNetCoreMasterAPIAssignment2.Filters;
using DomainModels;
using Infrastructure.Data.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Repositories;
using Services;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddSwaggerGen(x =>
{
x.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "Please insert JWT token with bearer into field",
Name = "Authorization",
Type = SecuritySchemeType.ApiKey
});
x.AddSecurityRequirement(new OpenApiSecurityRequirement {
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] { }
}
});
});
builder.Services.AddScoped<IItemService, ItemService>();
builder.Services.AddScoped<IItemRepository, ItemRepository>();
builder.Services.AddDbContext<ItemDbContext>(opt =>
{
opt.UseSqlServer(builder.Configuration.GetConnectionString("default"));
});
builder.Services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<ItemDbContext>()
.AddDefaultTokenProviders();
SecurityKey key = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(builder.Configuration["jwt:secret"]));
builder.Services.Configure<JWTOptions>(_ => _.SecurityKey = key);
builder.Services.AddAuthentication(opt =>
{
opt.DefaultAuthenticateScheme = "Bearer";
opt.DefaultChallengeScheme = "Bearer";
opt.DefaultScheme = "Bearer";
})
.AddJwtBearer(opt =>
{
opt.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
IssuerSigningKey = key
};
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI();
}
else
{
app.UseExceptionHandler("/error");
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"default": "Server=localhost;Database=EfCoreDb;Trusted_Connection=True;"
},
"jwt": {
"secret": "5a927360-790b-4ba5-bae1-09aa98364090"
}
}
when i add the [Authorized] attribute to a controller, i get the following error
invalid_token can be caused by a lot of cases.
Try to view the root cause by handling the OnChallenge method (in particular the content of context.AuthenticateFailure):
return builder.AddJwtBearer(options =>
{
options.Authority = issuer;
options.Audience = audience;
options.TokenValidationParameters = new TokenValidationParameters()
{
ClockSkew = new System.TimeSpan(0, 0, 30)
};
options.Events = new JwtBearerEvents()
{
OnChallenge = context =>
{
context.HandleResponse();
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
context.Response.ContentType = "application/json";
// Ensure we always have an error and error description.
if (string.IsNullOrEmpty(context.Error))
context.Error = "invalid_token";
if (string.IsNullOrEmpty(context.ErrorDescription))
context.ErrorDescription = "This request requires a valid JWT access token to be provided";
// Add some extra context for expired tokens.
if (context.AuthenticateFailure != null && context.AuthenticateFailure.GetType() == typeof(SecurityTokenExpiredException))
{
var authenticationException = context.AuthenticateFailure as SecurityTokenExpiredException;
context.Response.Headers.Add("x-token-expired", authenticationException.Expires.ToString("o"));
context.ErrorDescription = $"The token expired on {authenticationException.Expires.ToString("o")}";
}
return context.Response.WriteAsync(JsonSerializer.Serialize(new
{
error = context.Error,
error_description = context.ErrorDescription
}));
}
};
});
source: https://sandrino.dev/blog/aspnet-core-5-jwt-authorization#configuring-jwt-bearer-authentication
In my case, it was resolved by updating some nuget, specially Microsoft.IdentityModel.Tokens : https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/1792

SSE "data" field not being received by Dart http.client

I'm building a Flutter app that receives SSE from a server and translates them to specific notifications. The server is a Spring Boot app returning events containing "event:" and "data:" fields:
public void pushNotification(String username, PushEvent event) {
var emitter = emitters.get(username);
if (emitter == null) {
return;
}
try {
emitter.send(event.toSseEvent());
} catch (IOException e) {
log.debug("Could not send event for user " + username);
emitters.remove(username);
}
}
public class PushEvent {
private String type;
private Map<String, Object> body;
public SseEmitter.SseEventBuilder toSseEvent() {
return SseEmitter.event().name(type).data(body);
}
}
On the Flutter app, I use the Dart http package to open a Stream and receive the events:
Future<void> subscribe() async {
if (!_userModel.hasAuthentication()) {
return;
}
var user = _userModel.user as AuthenticatedUser;
var username = user.username;
var token = _userModel.getToken();
var uri = Uri.https(ApiUtils.API_BASE, '/api/push/subscribe/$username');
try {
var client = http.Client();
_client = client;
var request = new http.Request("GET", uri);
request.headers["Accept"] = "text/event-stream";
request.headers["Cache-Control"] = "no-cache";
request.headers["Authorization"] = token;
var response = await client.send(request);
if (response.statusCode == 200) {
_isSubscribed = true;
response.stream.toStringStream().forEach((value) {
var event = ServerEvent.parse(value);
_handleEvents(event);
}).onError((error, stackTrace) {
log.info("Connection closed");
log.info(error);
log.info(stackTrace);
unsubscribe();
}).whenComplete(() {
log.info("Connection completed");
unsubscribe();
subscribe();
});
} else {
_isSubscribed = false;
}
notifyListeners();
} catch (e) {
unsubscribe();
log.warning("Could not subscribe to notifications");
log.warning(e);
}
}
However, when I receive an event containing data from the server, the data does not show on the log:
I/flutter (14779): event:FRIEND_REQUEST
I/flutter (14779): data:
I am certain the data is being sent by the server since the React app on the same domain decodes the SSE and shows the notifications as intended:
const subscribePush = () => {
const username = sessionStorage.getItem('loggedUsername');
const token = sessionStorage.getItem('token');
var es = new EventSourcePolyfill(
'/api/push/subscribe/' + username,
{
headers: {
"Authorization": token,
}
}
);
es.onerror = () => es.close();
es.addEventListener("FRIEND_REQUEST", e => handleFriendRequestEvent(e));
es.addEventListener("FRIEND_ACCEPT", e => handleFriendAcceptEvent(e));
}
const handleFriendRequestEvent = function (event) {
const username = sessionStorage.getItem("loggedUsername");
const data = JSON.parse(event.data);
const source = data.source;
if (source !== username) {
var note = `${source} solicitou sua amizade!`;
var newNotifs = notifications.concat(note);
setNotifications(newNotifs);
setNewNotifications(newNotifications + 1);
}
}
Could something be missing from the request on the Flutter app, or is it possibly a bug?
Your implementation looks strangely similar to this one:
https://github.com/stevenroose/dart-eventsource
Take a look at the client implementation and how the response in decoded using the decoder.dart file.

Unsupported Grant Type with CustomGrantValidator with IdentityServer 3

I'm trying to set up our IdentityServer solution to accept a custom Grant Validator. Our API project is accessed by to UIs, one that uses Password authentication (which is working) and now one that will use a 3rd party authentication.
In our API I've set up IdentityServer like so:
Startup.cs
public void Configuration(IAppBuilder app)
{
var factory = new IdentityServerServiceFactory()
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get());
var userService = new IdentityUserService();
factory.UserService = new Registration<IUserService>(resolver => userService);
factory.CustomGrantValidators.Add(
new Registration<ICustomGrantValidator, MyGrantValidator>());
var options = new IdentityServerOptions
{
SiteName = "My App Name",
SigningCertificate = Certificate.Get(),
Factory = factory
};
app.Map("/identity", identityServerApp =>
{
identityServerApp.UseIdentityServer(options);
});
}
MyGrantValidator.cs:
public class MyGrantValidator : ICustomGrantValidator
{
public async Task<CustomGrantValidationResult> ValidateAsync(ValidatedTokenRequest request)
{
// For now I just want a basic response. More logic will come later.
var authResult = new AuthenticateResult(
subject: "1234", // user.AccountId.ToString(),
name: "bob" //context.UserName
);
var grantResult = new CustomGrantValidationResult
{
IsError = authResult.IsError,
Error = authResult.ErrorMessage,
ErrorDescription = authResult.ErrorMessage,
Principal = authResult.User
};
return await Task.FromResult(grantResult);
}
public string GrantType => "myGrantType";
}
In my UI, I setup a client like this:
var owinContext = HttpContext.GetOwinContext();
var token = owinContext.Authentication.User.FindFirst(c => c.Type == "myToken")?.Value;
var tokenId = owinContext.Authentication.User.FindFirst(c => c.Type == ClaimTypes.Sid)?.Value;
var client = new TokenClient(
ConfigurationManager.AppSettings["IdentityServerBaseUrl"] + "/connect/token",
"MyUser",
ConfigurationManager.AppSettings["MyClientSecret"],
AuthenticationStyle.Custom
);
var tokenResponse = client.RequestCustomGrantAsync(
"myGrantType",
"read write",
new Dictionary<string, string>
{
{ "token", token },
{ "tokenId", tokenId }
}
).Result;
return Redirect(returnUrl);
When the Request is triggered, I get: unsupported_grant_type
What am I missing?
You're using a client called "MyUser" (weird name for a client, but ok). Is that client registered as one of the in-memory clients with grant type set to "custom"?

mvc6 unauthorized results in redirect instead

I have been trying to prevent the redirect when I return an NotAuthorized IActionResult from a Controller, but regardless of my attempts, NotAuthorized gets translated to a Redirect.
I have tried what is mentioned here (same issue, using older beta framework, I use 1.0.0-rc1-final). I do not have the Notifications namespace (has been removed in rc1-final).
This is my Login Controller:
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
return Ok(model);
}
if (result.IsLockedOut)
{
return new HttpStatusCodeResult((int)HttpStatusCode.Forbidden);
}
else
{
return HttpUnauthorized();
}
}
return HttpUnauthorized();
}
In Startup.cs I have tried variations over this:
services.Configure<CookieAuthenticationOptions>(o =>
{
o.LoginPath = PathString.Empty;
o.ReturnUrlParameter = PathString.Empty;
o.AutomaticChallenge = false;
});
Everytime a login fails (please ignore that the password is returned on Ok) and should result in an empty 401 page, I get a redirection to /Account/Login instead. What is the trick here?
The solution is not to configure CookieAuthenticationOptions directly, but do it via IdentityOptions like this:
services.Configure<IdentityOptions>(o =>
{
o.Cookies.ApplicationCookie.Events = new CookieAuthenticationEvents()
{
OnRedirectToLogin = ctx =>
{
if (ctx.Response.StatusCode == (int)HttpStatusCode.Unauthorized)
{
return Task.FromResult<object>(null);
}
ctx.Response.Redirect(ctx.RedirectUri);
return Task.FromResult<object>(null);
}
};
});
Taken from here ( Shawn Wildermuth --> ASP.NET 5 Identity and REST APIs --> Comment of "Mehdi Hanafi") and tested the API with Postman
config.Cookies.ApplicationCookie.Events = new CookieAuthenticationEvents()
{
OnRedirectToLogin = ctx =>
{
if (ctx.Request.Path.StartsWithSegments("/api") &&
ctx.Response.StatusCode == 200)
{
ctx.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
return Task.FromResult<object>(null);
}
else
{
ctx.Response.Redirect(ctx.RedirectUri);
return Task.FromResult<object>(null);
}
}
};
from Identity 2.0,
you'd need to add:
using Microsoft.AspNetCore.Authentication.Cookies;
and in ConfigureServices:
services.ConfigureApplicationCookie(options =>
{
options.Events = new CookieAuthenticationEvents
{
OnRedirectToLogin = (x =>
{
if (x.Request.Path.StartsWithSegments("/api") && x.Response.StatusCode == 200)
x.Response.StatusCode = 401;
return Task.CompletedTask;
}),
OnRedirectToAccessDenied = (x =>
{
if (x.Request.Path.StartsWithSegments("/api") && x.Response.StatusCode == 200)
x.Response.StatusCode = 403;
return Task.CompletedTask;
})
};
});
the segments check should of course be adjusted to your routes.
If you have some pages for which the redirect is desired and other URLs that should not have a redirect, see this question for a solution that uses the default redirect logic only for non-API URLs:
Suppress redirect on API URLs in ASP.NET Core

Can't get email or public profile facebook by ASP MVC5

I use MVC5 to get public profile user, but I just get ID and DisplayName of user. I have researched multi ways but have no result. This my setup snippet:
var options = new FacebookAuthenticationOptions
{
AppId = "xxx",
AppSecret = "xxx",
SignInAsAuthenticationType = "ExternalCookie",
Provider = new FacebookAuthenticationProvider
{
OnAuthenticated = async context =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken));
}
}
};
options.Scope.Add("email");
options.Scope.Add("public_profile");
app.UseFacebookAuthentication(options);
And I call request result in action
public async Task<ActionResult> Index()
{
var userDisplayModel = new UserDisplayModel();
var authenticateResult = await AuthenticationManager.AuthenticateAsync("ExternalCookie");
if (authenticateResult != null)
{
userDisplayModel = new UserDisplayModel()
{
DisplayName = authenticateResult.Identity.Claims.FirstOrDefault(t => t.Type == ClaimTypes.Name).Value
};
}
return View(userDisplayModel);
}