In my application, it sometimes fail to send mail and it is not domain specific (i.e. in gmail sometimes mail is sent and sometimes it is not as per client complain). What can be the reason, any idea? I m attaching the code of my identityConfig.cs
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using RoasterManagementSystem.Data.DbContext;
using RoasterManagementSystem.Model;
using System.Net;
using System.Net.Mail;
namespace RoasterManagementSystem.App
{
public class EmailService : IIdentityMessageService
{
public async Task SendAsync(IdentityMessage message)
{
var smtpConfig = new System.Net.Mail.SmtpClient();
var credential = (System.Net.NetworkCredential)smtpConfig.Credentials;
var fromAddress = new MailAddress(credential.UserName, "Roster Management Application");
var toAddress = new MailAddress(message.Destination);
string fromPassword = "123456Tt";
string subject = message.Subject;
string body = message.Body;
var smtp = new SmtpClient
{
Host = smtpConfig.Host,
Port = smtpConfig.Port,
EnableSsl = smtpConfig.EnableSsl,
DeliveryMethod = SmtpDeliveryMethod.Network,
UseDefaultCredentials = smtpConfig.UseDefaultCredentials,
Credentials = new NetworkCredential(credential.UserName, credential.Password)
};
var mail = new MailMessage(fromAddress, toAddress)
{
Subject = subject,
Body = body,
IsBodyHtml = true
};
using(smtp)
using (mail)
{
try
{
await smtp.SendMailAsync(mail);
}
catch (Exception ex)
{
//If email sending failed then do nothing for now
}
}
}
}
public class SmsService : IIdentityMessageService
{
public Task SendAsync(IdentityMessage message)
{
// Plug in your SMS service here to send a text message.
return Task.FromResult(0);
}
}
// Configure the application user manager used in this application. UserManager is defined in ASP.NET Identity and is used by the application.
public class ApplicationUserManager : UserManager<ApplicationUser>
{
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{
this.UserValidator = new UserValidator<ApplicationUser>(this)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// Configure validation logic for passwords
this.PasswordValidator = new PasswordValidator
{
RequiredLength = 8,
RequireNonLetterOrDigit = false,
RequireDigit = true,
RequireLowercase = true,
RequireUppercase = true,
};
// Configure user lockout defaults
this.UserLockoutEnabledByDefault = true;
this.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
this.MaxFailedAccessAttemptsBeforeLockout = 5;
// Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user
// You can write your own provider and plug it in here.
this.RegisterTwoFactorProvider("Phone Code", new PhoneNumberTokenProvider<ApplicationUser>
{
MessageFormat = "Your security code is {0}"
});
this.RegisterTwoFactorProvider("Email Code", new EmailTokenProvider<ApplicationUser>
{
Subject = "Security Code",
BodyFormat = "Your security code is {0}"
});
this.EmailService = new EmailService();
this.SmsService = new SmsService();
var dataProtectionProvider = Startup.DataProtectionProvider;
if (dataProtectionProvider != null)
{
this.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
}
}
}
// Configure the application sign-in manager which is used in this application.
public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
{
public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager)
: base(userManager, authenticationManager)
{
}
public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
{
return user.GenerateUserIdentityAsync((ApplicationUserManager) UserManager);
}
}
}
Related
I faced problem with hashing, salting and verifying password in ASP.NET.
I am creating a new User and then using hashing method.
But when I try to get some resources which requires Authorization and I
enter the same username and password as I saved in database the result is failed.
Here is my password hasher class:
using Microsoft.AspNetCore.Identity;
namespace FlowerShop.ApplicationServices.Components.PasswordHasher
{
public class BCryptPasswordHasher<User> : IPasswordHasher<User> where User : class
{
public string HashPassword(User user, string password)
{
return BCrypt.Net.BCrypt.HashPassword(password, 12);
}
public PasswordVerificationResult VerifyHashedPassword(User user, string hashedPassword, string providedPassword)
{
var isValid = BCrypt.Net.BCrypt.Verify(providedPassword, hashedPassword);
if (isValid && BCrypt.Net.BCrypt.PasswordNeedsRehash(hashedPassword, 12))
{
return PasswordVerificationResult.SuccessRehashNeeded;
}
return isValid ? PasswordVerificationResult.Success : PasswordVerificationResult.Failed;
}
}
This is my authentication class:
public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private readonly IQueryExecutor queryExecutor;
private readonly IPasswordHasher<User> passwordHasher;
public BasicAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock,
IQueryExecutor queryExecutor, IPasswordHasher<User> passwordHasher)
: base(options, logger, encoder, clock)
{
this.queryExecutor = queryExecutor;
this.passwordHasher = passwordHasher;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var endpoint = Context.GetEndpoint();
if (endpoint?.Metadata?.GetMetadata<IAllowAnonymous>() != null)
{
return AuthenticateResult.NoResult();
}
if (!Request.Headers.ContainsKey("Authorization"))
{
return AuthenticateResult.Fail("Missing Authorization Header");
}
User user = null;
try
{
var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
var credentials = Encoding.UTF8.GetString(credentialBytes).Split(new[] { ':' }, 2);
var username = credentials[0];
var providedPassword = passwordHasher.HashPassword(user, credentials[1]);
var query = new GetUserQuery()
{
UserName = username
};
user = await this.queryExecutor.Execute(query);
if (user == null || passwordHasher.VerifyHashedPassword(user, user.PasswordHash, providedPassword)
== PasswordVerificationResult.Failed)
{
return AuthenticateResult.Fail("Invalid Authorization Header");
}
}
catch
{
return AuthenticateResult.Fail("Invalid Authorization Header");
}
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.Role, user.Role.ToString()),
new Claim(ClaimTypes.Email, user.Email),
};
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
}
And in this place I am creating a new User:
using MediatR;
using Microsoft.AspNetCore.Identity;
using System.Threading;
using System.Threading.Tasks;
public class AddUserHandler : IRequestHandler<AddUserRequest,
AddUserResponse>
{
private readonly ICommandExecutor commandExecutor;
private readonly IQueryExecutor queryExecutor;
private readonly IMapper mapper;
private readonly IPasswordHasher<User> passwordHasher;
public AddUserHandler(ICommandExecutor commandExecutor,
IQueryExecutor queryExecutor,
IMapper mapper, IPasswordHasher<User> passwordHasher)
{
this.commandExecutor = commandExecutor;
this.queryExecutor = queryExecutor;
this.mapper = mapper;
this.passwordHasher = passwordHasher;
}
public async Task<AddUserResponse> Handle(AddUserRequest
request, CancellationToken cancellationToken)
{
var query = new GetUserQuery()
{
UserName = request.UserName,
Email = request.Email
};
var getUser = await this.queryExecutor.Execute(query);
if (getUser != null)
{
if (getUser.UserName == request.UserName)
{
return new AddUserResponse()
{
Error = new ErrorModel(ErrorType.ValidationError +
"! The name is already taken.")
};
}
if (getUser.Email == request.Email)
{
return new AddUserResponse()
{
Error = new ErrorModel(ErrorType.ValidationError +
"! Email address is in use.")
};
}
return new AddUserResponse()
{
Error = new ErrorModel(ErrorType.Conflict)
};
}
request.PasswordHash = passwordHasher.HashPassword(getUser,
request.Password);
var user = this.mapper.Map<User>(request);
var command = new AddUserCommand()
{
Parameter = user
};
var addedUser = await this.commandExecutor.Execute(command);
var response = new AddUserResponse()
{
Data =
this.mapper.Map<Domain.Models.UserDTO>(addedUser)
};
return response;
}
}
This is my Startup.cs :
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication("BasicAuthentication")
.AddScheme<AuthenticationSchemeOptions,
BasicAuthenticationHandler>("BasicAuthentication", null);
services.AddScoped<IPasswordHasher<User>,
BCryptPasswordHasher<User>>();
}
Maybe first of all, is it all correct implemented?
Is hash in AddUserHandler correct assigned to request.PasswordHash?
How to retrieve salt and assign to request.PasswordSalt?
Sorry for any unclear things if they occur.
Any feedback and help will be appreciated.
Thanks in advance :)
Edit:
for example if I add user with password "pass123" and it is stored in database as 'user.PasswordHash = "$2a$12$Iqpy7FyQh/pt2O8upTtG5eOQKzo1V395wRNdAXPpp5Qf.NQ.KxUyy"' and provided password after hashing is 'providedPassword = "$2a$12$9vSz8Sw/WtmqGY6jyDiTleN/btZ0wXJkXdoB3sDpANVIIDGBpaqT."'
I fixed the bug if anyone needs to use it in thee future.
The problem was in my authentication class.
In place of:
var username = credentials[0];
var providedPassword = passwordHasher.HashPassword(user, credentials[1]);
Should be:
var username = credentials[0];
var providedPassword = credentials[1];
I am sure that I have checked it a few times but somehow didn't work then. Anyway, it finally works properly.
Following code is working locally very fine.
The users receive All pushes (notifications) when I send them via local PC.
But I need use proxy on the server and this code fires the error: "Failed to establish a connection: Network is unreachable".
Plz help me setup the proxy for this code.
using System;
using MediatR;
using System.IO;
using FirebaseAdmin;
using System.Threading;
using System.Threading.Tasks;
using FirebaseAdmin.Messaging;
using Google.Apis.Auth.OAuth2;
namespace JMGlobal.Handlers
{
public class PushHandler : IRequestHandler<Request_Push, Response_Push>
{
public async Task<Response_Push> Handle(Request_Push request, CancellationToken cancellationToken)
{
try
{
var defaultApp = FirebaseApp.DefaultInstance;
if (defaultApp == null)
{
var keyPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "jKey.json");
defaultApp = FirebaseApp.Create(new AppOptions()
{
Credential = GoogleCredential.FromFile(keyPath),
// HttpClientFactory --- ????
});
}
var message = new Message()
{
Token = request.FirebaseToken,
Apns = new ApnsConfig()
{
Aps = new FirebaseAdmin.Messaging.Aps()
{
Alert = new ApsAlert()
{
Title = "push",
Body = request.PushMessage
}
}
},
Notification = new Notification
{
Title = "System Notification",
Body = $"Message: {request.PushMessage}"
}
};
var messaging = FirebaseMessaging.DefaultInstance;
var result = await messaging.SendAsync(message); // here fires the error: "Failed to establish a connection: Network is unreachable"
return new Response_Push()
{ .... };
}
catch (Exception ex)
{..... }
}
}
}
Working version below.
using System;
using MediatR;
using System.IO;
using FirebaseAdmin;
using System.Threading;
using System.Diagnostics;
using System.Threading.Tasks;
using FirebaseAdmin.Messaging;
using Google.Apis.Auth.OAuth2;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Google.Apis.Http;
using System.Net.Http;
using System.Net;
using Microsoft.Extensions.Options;
namespace JMGlobal.Handlers
{
public class ProxyHttpClientFactory2 : Google.Apis.Http.HttpClientFactory
{
private readonly string proxyAddress;
private readonly bool proxyUseProxy;
private readonly bool proxyBypassOnLocal;
public ProxyHttpClientFactory2(string proxyAddress, bool proxyUseProxy, bool proxyBypassOnLocal)
{
this.proxyAddress = proxyAddress;
this.proxyUseProxy = proxyUseProxy;
this.proxyBypassOnLocal = proxyBypassOnLocal;
}
protected override HttpMessageHandler CreateHandler(CreateHttpClientArgs args)
{
var proxy = new WebProxy(Address: proxyAddress, BypassOnLocal: proxyBypassOnLocal, BypassList: null, Credentials: null);
var webRequestHandler = new HttpClientHandler()
{
Proxy = proxy,
UseProxy = proxyUseProxy,
UseCookies = false
};
return webRequestHandler;
}
}
public class PushHandler : IRequestHandler<Request_PushFromBackend, Response_PushFromBackend>
{
private readonly IDBCommander _commander;
private readonly ILogger<PushHandler> _logger;
public PushFromBackendHandler(ILogger<PushHandler> logger, IDBCommander commander)
{
_logger = logger;
_commander = commander;
}
public async Task<Response_PushFromBackend> Handle(Request_PushFromBackend request, CancellationToken cancellationToken)
{
var sw = Stopwatch.StartNew();
bool isProxyUsing = false;
try
{
var resultFCMcreds = await _commander.Get_FCMcredentials(); // settings from DB
var resultProxy = await _commander.Get_ProxySettings(); // settings from DB
var defaultApp = FirebaseApp.DefaultInstance;
if (defaultApp == null)
{
var serviceAccountEmail = resultFCMcreds?.ServiceAccountEmail;
var PrivateKey = resultFCMcreds?.PrivateKey;
var credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
ProjectId = resultFCMcreds?.ProjectId,
HttpClientFactory = new ProxyHttpClientFactory2(resultProxy?.Address, (bool)resultProxy?.UseProxy, (bool)resultProxy?.BypassOnLocal),
}.FromPrivateKey(PrivateKey));
defaultApp = FirebaseApp.Create(new AppOptions()
{
Credential = GoogleCredential.FromServiceAccountCredential(credential),
HttpClientFactory = new ProxyHttpClientFactory2(resultProxy?.Address, (bool)resultProxy?.UseProxy, (bool)resultProxy?.BypassOnLocal)
});
}
FirebaseAdmin.Messaging.Aps aps_data = new Aps();
if (request.PushMode == 1)
{
aps_data.ContentAvailable = true;
}
var message = new Message()
{
//Topic = "news",
Token = request.FirebaseToken,
Apns = new ApnsConfig()
{
Aps = aps_data
},
Data = new Dictionary<string, string>()
{
["Changed"] = "true",
},
Notification = (request.PushMode == 1) ? null : new Notification
{
Title = $"System Notification {((request.PushMode == 1) ? " (SILENT)" : " (NORMAL)")}",
Body = $"Message: {request.PushMessage}"
}
};
var messaging = FirebaseMessaging.DefaultInstance;
var result = await messaging.SendAsync(message);
_logger.LogInformation($"result: {result}");
return new Response_PushFromBackend()
{
....
};
}
catch (Exception ex)
{
return new Response_PushFromBackend()
{
Error = ex.Message
};
}
}
}
}
I am facing a problem that would like help.
I am developing a background process that will be listening to a queue in rabbitmq server.
It is OK if I run it in a .net core console application. However I would like to do it in a more elegant way such as web service (which has given me a lot of trouble where it does not work when installed) or an IIS hosted web application.
I face a problem of Scoped Service when I try to host the service (IHostedService) in .net core web application.
The code below is working fine in a console application. How can make it run as an IHostedService in a .net core web application.
What am I supposed to change.
Your help is appreciated.
CODE:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using PaymentProcessor.Models;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using Microsoft.EntityFrameworkCore;
namespace PaymentProcessor
{
public class PaymentProcessingService : HostedService
{
IConfiguration configuration;
private EntitiesContext claimsContext;
private string connectionString;
private string HostName = "";
private string UserName = "";
private string Password = "";
private static int MaxRetries;
private IConnectionFactory factory;
private IConnection connection;
private IModel channel;
public PaymentProcessingService(IConfiguration configuration)
{
this.configuration = configuration;
this.connectionString = configuration.GetConnectionString ("StagingContext");
claimsContext = new EntitiesContext(connectionString);
HostName = this.configuration.GetValue<string>("Settings:HostName");
UserName = this.configuration.GetValue<string>("Settings:UserName");
Password = this.configuration.GetValue<string>("Settings:Password");
MaxRetries = this.configuration.GetValue<string>("Settings:MaxRetries").ConvertTo<int>();
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
connect:
factory = new ConnectionFactory { HostName = HostName, UserName = UserName, Password = Password };
try
{
connection = factory.CreateConnection();
channel = connection.CreateModel();
channel.ExchangeDeclare("payment_rocessing_exchange", "topic");
channel.QueueDeclare("payment_processing_queue", true, false, false, null);
channel.QueueBind("payment_processing_queue", "payment_processing_exchange", "processing");
var queueArgs = new Dictionary<string, object>
{
{ "x-dead-letter-exchange", "payment_processing_exchange" },
{"x-dead-letter-routing-key", "processing_retry"},
{ "x-message-ttl", 10000 }
};
channel.ExchangeDeclare("payment_rocessing_exchange", "topic");
channel.QueueDeclare("payment_processing_retry_queue", true, false, false, queueArgs);
channel.QueueBind("payment_processing_retry_queue", "payment_processing_exchange", "processing_retry", null);
channel.ExchangeDeclare("payment_processing_exchange", "topic");
channel.QueueDeclare("payment_processing_error_queue", true, false, false, null);
channel.QueueBind("payment_processing_error_queue", "payment_processing_exchange", "processing_error", null);
channel.ExchangeDeclare("payment_processing_exchange", "topic");
channel.QueueDeclare("payment_integration_queue", true, false, false, null);
channel.QueueBind("payment_integration_queue", "payment_processing_exchange", "integration", null);
channel.BasicQos(0, 1, false);
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var message = ea.Body.DeSerializeText();
try
{
var saveBundle = JObject.Parse(message);
var msg = (dynamic)((dynamic)saveBundle).Message;
string referenceNo = (string)msg.ReferenceNo;
var parameters = new[]
{
new SqlParameter
{
DbType = DbType.String,
ParameterName = "ReferenceNo",
Value =referenceNo
}
};
var result = claimsContext.Database.ExecuteSqlCommand("dbo.PaymentReferencesProcessSingle #ReferenceNo", parameters);
IBasicProperties props = channel.CreateBasicProperties();
props.Persistent = true;
props.ContentType = "text/plain";
props.DeliveryMode = 2;
channel.BasicPublish("payment_processing_exchange", "integration", props, (new MessageEnvelope { RetryCounts = 0, Message = JObject.FromObject(new { ReferenceNo = referenceNo }) }).Serialize());
}
catch (Exception ex)
{
MessageEnvelope envelope = JsonConvert.DeserializeObject<MessageEnvelope>(message);
if (envelope.RetryCounts < MaxRetries)
{
int RetryCounts = envelope.RetryCounts + 1;
MessageEnvelope messageEnvelope = new MessageEnvelope { RetryCounts = RetryCounts, Message = envelope.Message };
var data = messageEnvelope.Serialize();
channel.BasicPublish("payment_processing_exchange", "processing_retry", null, data);
}
else
{
var data = envelope.Serialize();
channel.BasicPublish("payment_processing_exchange", "processing_error", null, data);
}
}
finally
{
channel.BasicAck(ea.DeliveryTag, false);
}
};
channel.BasicConsume(queue: "payment_processing_queue", autoAck: false, consumer: consumer);
}
catch (Exception ex)
{
Thread.Sleep(10000);
goto connect;
}
}
}
}
and then
services.AddScoped<IHostedService, PaymentProcessingService>();
As Dekim mentioned a service should be registered.
Please have a look on an example I created on GitHub.
Program.cs looks like this:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Threading.Tasks;
namespace Core
{
internal class Program
{
static async Task Main(string[] args)
{
await new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<ServiceRabbitMQ>(); // register our service here
})
.RunConsoleAsync();
}
}
}
Because the IHostedService requires the creation of a special scope according to the documentation.
From the Microsoft documentation cited in the above answer :
No scope is created for a hosted service by default.
Consider using : services.AddHostedService<MyHostedService>();
Trying to create registration page provided by asp.net identity. I am getting the following error:
System.ArgumentNullException: 'Value cannot be null.
Parameter name: manager'
Please help me to find out why it is showing null. Any help is highly appreciated.
My code for the register.aspx.cs is:
var manager = Context.GetOwinContext().GetUserManager<ApplicationUserManager>();
var signInManager = Context.GetOwinContext().Get<ApplicationSignInManager>();
var user = new ApplicationUser() { Email = EmailId.Text };
IdentityResult result = manager.Create(user, Password.Text);
if (result.Succeeded)
{
signInManager.SignIn(user, isPersistent: false, rememberBrowser: false);
IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response);
}
else
{
ErrorMessage.Text = result.Errors.FirstOrDefault();
}
And I have not made any changes in this part. My ApplicationUserManager code looks like this:
public class ApplicationUserManager : UserManager<ApplicationUser>
{
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{
}
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
// Configure validation logic for usernames
manager.UserValidator = new UserValidator<ApplicationUser>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = true,
RequireDigit = true,
RequireLowercase = true,
RequireUppercase = true,
};
// Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user
// You can write your own provider and plug it in here.
manager.RegisterTwoFactorProvider("Phone Code", new PhoneNumberTokenProvider<ApplicationUser>
{
MessageFormat = "Your security code is {0}"
});
manager.RegisterTwoFactorProvider("Email Code", new EmailTokenProvider<ApplicationUser>
{
Subject = "Security Code",
BodyFormat = "Your security code is {0}"
});
// Configure user lockout defaults
manager.UserLockoutEnabledByDefault = true;
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
manager.MaxFailedAccessAttemptsBeforeLockout = 5;
manager.EmailService = new EmailService();
manager.SmsService = new SmsService();
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
}
return manager;
}
}
Most likely you are missing this line
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
in your Startup.Auth.cs file.
As in this bit:
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create); // <-- this is missing
// etc...
In our asp.net MVC5 web site we're authenticating against multiple ADFS servers. One of these requests that we sign (and preferably encrypt) our request.
We're using OWIN and the UseWsFederationAuthentication extension method to set up the options for each ADFS server (see below).
var adfsLoginProviderOptions = new WsFederationAuthenticationOptions
{
MetadataAddress = adfsLoginProvider.MetadataUrl,
Wtrealm = AppSettings.FirstAgendaWtRealm,
AuthenticationMode = AuthenticationMode.Passive,
AuthenticationType = adfsLoginProvider.Name,
CallbackPath = new PathString("/adfs/callback"),
UseTokenLifetime = true
};
app.UseWsFederationAuthentication(adfsLoginProviderOptions);
My problem is, I don't see a obvious option to set up request signing and encryption and I can't seem to find anyone else who has done this.
I did some research and found the following.
I needed to register to SecurityTokenHandlers:
One for decrypting the encrypted token
One for validating the signed token
They are registered as follows:
var adfsLoginProviderOptions = new WsFederationAuthenticationOptions
{
MetadataAddress = adfsLoginProvider.MetadataUrl,
Wtrealm = "http://[your-realm]",
AuthenticationMode = AuthenticationMode.Passive,
AuthenticationType = adfsLoginProvider.Name,
UseTokenLifetime = false,
CallbackPath = new PathString("/adfs/callback/" + adfsLoginProvider.ID.ToString()),
TokenValidationParameters = new TokenValidationParameters
{
AuthenticationType = adfsLoginProvider.Name
},
SecurityTokenHandlers = new SecurityTokenHandlerCollection
{
new EncryptedSecurityTokenHandler(new X509CertificateStoreTokenResolver(StoreName.My,StoreLocation.LocalMachine)),
new SamlSecurityTokenHandler
{
CertificateValidator = X509CertificateValidator.None,
Configuration = new SecurityTokenHandlerConfiguration()
{
AudienceRestriction = audienceRestriction,
IssuerNameRegistry = issuerRegistry
}
}
},
};
The EncryptedSecurityTokenHandler is implemented as follows:
public class EncryptedSecurityTokenHandler : System.IdentityModel.Tokens.EncryptedSecurityTokenHandler, ISecurityTokenValidator
{
public EncryptedSecurityTokenHandler(SecurityTokenResolver securityTokenResolver)
{
Configuration = new SecurityTokenHandlerConfiguration
{
ServiceTokenResolver = securityTokenResolver
};
}
public override bool CanReadToken(string securityToken)
{
return base.CanReadToken(new XmlTextReader(new StringReader(securityToken)));
}
public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
{
// Read token will decrypt it and look for another SecurityTokenHandler in the same collection to do the actual validation
validatedToken = ReadToken(new XmlTextReader(new StringReader(securityToken)), Configuration.ServiceTokenResolver);
if (ContainingCollection != null)
{
var identities = ContainingCollection.ValidateToken(validatedToken);
var principal = new ClaimsPrincipal(identities.First());
return principal;
}
return new ClaimsPrincipal(base.ValidateToken(validatedToken));
}
public int MaximumTokenSizeInBytes { get; set; }
}
And the SamlSecurityTokenHandler:
public class SamlSecurityTokenHandler : System.IdentityModel.Tokens.SamlSecurityTokenHandler, ISecurityTokenValidator
{
public override bool CanReadToken(string securityToken)
{
return base.CanReadToken(XmlReader.Create(new StringReader(securityToken)));
}
public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters,
out SecurityToken validatedToken)
{
validatedToken = ReadToken(new XmlTextReader(new StringReader(securityToken)), Configuration.ServiceTokenResolver);
var identities = ValidateToken(validatedToken);
var newIdentities = identities.Select(d => new ClaimsIdentity(d.Claims, "ExternalCookie"));
var claimsPrincipal = new ClaimsPrincipal(newIdentities);
return claimsPrincipal; ;
}
public override ReadOnlyCollection<ClaimsIdentity> ValidateToken(SecurityToken token)
{
var identities = base.ValidateToken(token);
return identities
}
public int MaximumTokenSizeInBytes { get; set; }
}
Audience restriction is the realm of the application:
var audienceRestriction = new AudienceRestriction(AudienceUriMode.Always);
audienceRestriction.AllowedAudienceUris.Add(new Uri(http://[your-realm]));
IssuerRegistry is the registry of the issuer signing certificates:
var issuerRegistry = new ConfigurationBasedIssuerNameRegistry();
issuerRegistry.AddTrustedIssuer(adfsLoginProvider.SigningCertThumbprint, adfsLoginProvider.Issuer);