ConfirmEmailAsync() method is not working - email

I am having issue in confirming new user email. the Confirm email link works for first 20 minutes , but after 50 minutes the link expires. I have set the token expiration time to 24 hours. Please help me in resolving this issue. I am stuck on it for last 2 days:(.My code is as follows:
I am setting the token lifetime in Create() method in ApplicationUserManager as following:
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
userManager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"))
{
TokenLifespan = _settings.ConfirmationAndResetTokenExpirationTimeSpan
};
}
And then In AccountsController, the Create method for new user is geiven below. The SendEmailAsync method consist of email subject, email body, generated password and the callback uri.
[Authorize(Roles = Roles.Bam.Name.Admin)]
[HttpPost]
[Route(Routes.Accounts.Template.Create, Name = Routes.Accounts.Name.Create)]
public async Task<IHttpActionResult> Create(CreateUserBindingModel createUserBindingModel)
{
IHttpActionResult result;
var memberNameExists = UserManager.Users.Any(x => x.MemberName.ToLower() == createUserBindingModel.MemberName.ToLower());
if (!memberNameExists)
{
var applicationUser = new ApplicationUser
{
UserName = createUserBindingModel.Email,
Email = createUserBindingModel.Email,
FirstName = createUserBindingModel.FirstName,
LastName = createUserBindingModel.LastName,
Company = createUserBindingModel.Company,
Location = createUserBindingModel.Location,
PhoneNumber = createUserBindingModel.PhoneNumber,
MemberName = createUserBindingModel.MemberName,
LastLoginDate = SqlDateTime.MinValue.Value,
CreateDate = DateTime.Now,
CreatedBy = User.Identity.GetUserId(),
UpdateDate = DateTime.Now,
UpdatedBy = User.Identity.GetUserId(),
TwoFactorEnabled = createUserBindingModel.TwoFactorEnabled,
SecurityResetRequired = true,
PasswordExpirationDate = DateTime.Now.AddDays(Convert.ToDouble(ConfigurationManager.AppSettings["PasswordExpirationDays"]))
};
if (!string.IsNullOrEmpty(createUserBindingModel.AvatarBase64))
{
var avatarBytes = Convert.FromBase64String(createUserBindingModel.AvatarBase64);
var resizedAvatarBytes = ImageResizer.ResizeImage(avatarBytes, _avatarWidth, _avatarHeight);
applicationUser.UserAvatar = new ApplicationUserAvatar
{
Avatar = resizedAvatarBytes
};
}
var generatedPassword = PasswordGenerator.GenerateStrongPassword(10, 10);
var identityResult = await UserManager.CreateAsync(applicationUser, generatedPassword);
if (identityResult.Succeeded)
{
await UserManager.AddToRolesAsync(applicationUser.Id, createUserBindingModel.Roles.ToArray());
var token = await UserManager.GenerateEmailConfirmationTokenAsync(applicationUser.Id);
var callbackUri = string.Format("{0}?userId={1}&token={2}", createUserBindingModel.EmailConfirmationCallbackUri, applicationUser.Id, HttpUtility.UrlEncode(token));
await UserManager.SendEmailAsync(applicationUser.Id, Email.Confirmation.Subject, string.Format(Email.Confirmation.Body, string.Format("{0} {1}", applicationUser.FirstName, applicationUser.LastName), callbackUri, generatedPassword, _settings.AccessTokenExpirationTimeSpan.TotalHours));
var userUrl = new Uri(Url.Link(Routes.Accounts.Name.Get, new { id = applicationUser.Id }));
var roles = await UserManager.GetRolesAsync(applicationUser.Id);
var contract = _accountsMapper.ToContract(applicationUser, roles);
result = Created(userUrl, contract);
}
else
{
result = GetErrorResult(identityResult);
}
}
else
{
ModelState.AddModelError(string.Empty, "Member Name already exists!");
result = BadRequest(ModelState);
}
return result;
}
Once the email is generated the UI has following JS angular code which gets executed and the provide the userid and token to service.
Angular JS code:
angular.module('confirmEmailModule').factory('confirmEmailFactory', function ($http) {
var factory = {};
factory.confirmEmail = function(userId, token) {
var encodedToken = encodeURIComponent(token);
var uri = '/identity/api/accounts/confirmemail?userId=' + userId + '&token=' + token;
return $http.post(uri);
}
return factory;
});
and the Service is :
[AllowAnonymous]
[HttpPost]
[Route(Routes.Accounts.Template.ConfirmEmail, Name = Routes.Accounts.Name.ConfirmEmail)]
public async Task<IHttpActionResult> ConfirmEmail([FromUri] string userId, [FromUri] string token)
{
//var decodedToken = HttpUtility.UrlDecode(token);
var identityResult = await UserManager.ConfirmEmailAsync(userId, token);
var result = identityResult.Succeeded ? StatusCode(HttpStatusCode.NoContent) : GetErrorResult(identityResult);
return result;
}
Please advice.

I found the solution to this issue. I am posting it if somebody faced the same issue. In my case the services and web API were on different servers. Different machine keys caused this issue. So I generated the machine key for my Web application and posted the same machine key in web.config file of Identity service. After that it worked. For more information on generating machine key, following link is helpful.
http://gunaatita.com/Blog/How-to-Generate-Machine-Key-using-IIS/1058

This is what worked for me. Hope it helps out;
public async Task<IActionResult> ConfirmEmail(string userId, string token)
{
if (userId == null || token == null)
{
return RedirectToAction("employees", "home");
}
var user = await userManager.FindByIdAsync(userId);
if (user == null)
{
ViewBag.ErrorMessage = $"The User ID {userId} is invalid";
return View("NotFound");
}
var result = await userManager.ConfirmEmailAsync(user, Uri.EscapeDataString(token));
if (result != null)
{
user.EmailConfirmed = true;
await userManager.UpdateAsync(user);
return View();
}
}

Related

Asp.Net Core Identity Create User

I have trouble creating a user. The process runs correctly, reads data to user, redirects to Home / Index, but there is no new user in the database.
I have the same code in DbSeeder and the users are created correctly there.
[HttpPost]
public async Task<IActionResult> Create(WorkerVM model)
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByNameAsync(model.Username);
if (user == null)
{
user = new User()
{
FirstName = model.FirstName,
LastName = model.LastName,
Email = model.Username,
UserName = model.Username
};
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await _userManager.AddToRoleAsync(user, "Worker");
}
return RedirectToAction("Index", "Home");
}
}
ModelState.AddModelError("", "Registration Failed");
return View();
}
The most possible reason that the user is not created in db is the new password doesn't fit the password criteria.
Check the password criterias and fix the password. Also fixed your code by moving "}" behind the first return operator:
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await _userManager.AddToRoleAsync(user, "Worker");
return RedirectToAction("Index", "Home");
}
you can manage your password criteria adding this code to startup
services.Configure<IdentityOptions>(x => {
x.Password.RequireDigit = false;
x.Password.RequiredLength = 2;
x.Password.RequireUppercase = false;
x.Password.RequireLowercase = false;
x.Password.RequireNonAlphanumeric = false;
x.Password.RequiredUniqueChars = 0;
x.Lockout.AllowedForNewUsers = true;
x.Lockout.MaxFailedAccessAttempts = 5;
x.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromSeconds(30);
});

Xamarin Essentials Unable to exchange Okta authorization code for token

I was using OpenID and we have to switch to Xamarin.Essentials.WebAuthenticator.
I can get an authorization code from Okta using WebAuthenticator.AuthenticateAsync().
But, everything I try to then translate that code into an access token returns 400 Bad Request.
Okta's API error is "E0000021: HTTP media type not supported exception" and it goes on to say, "Bad request. Accept and/or Content-Type headers likely do not match supported values."
I have tried to follow https://developer.okta.com/blog/2020/07/31/xamarin-essentials-webauthenticator as much as possible, but we are not using the hybrid grant type like he is.
We are using only Authorization Code, which means I have to make a secondary call, and I have spent two days trying to figure out how.
private async Task LoginOktaAsync()
{
try
{
var loginUrl = new Uri(BuildAuthenticationUrl()); // that method is down below
var callbackUrl = new Uri("com.oktapreview.dev-999999:/callback"); // it's not really 999999
var authenticationResult = await Xamarin.Essentials.WebAuthenticator.AuthenticateAsync(loginUrl, callbackUrl);
string authCode;
authenticationResult.Properties.TryGetValue("code",out authCode);
// Everything works fine up to this point. I get the authorization code.
var url = $"https://dev-999999.oktapreview.com/oauth2/default/v1/token"
+"?grant_type=authorization_code"
+$"&code={authCode}&client_id={OktaConfiguration.ClientId}&code_verifier={codeVerifier}";
var request = new HttpRequestMessage(HttpMethod.Post, url);
var client = new HttpClient();
var response = await client.SendAsync(request); // this generates the 400 error.
}
catch(Exception e)
{
Debug.WriteLine($"Error: {e.Message}");
}
}
Here are the methods that produce the login url and a couple of other things:
public string BuildAuthenticationUrl()
{
var state = CreateCryptoGuid();
var nonce = CreateCryptoGuid();
CreateCodeChallenge();
var url = $"https://dev-999999.oktapreview.com/oauth2/default/v1/authorize?response_type=code"
+ "&response_mode=fragment"
+ "&scope=openid%20profile%20email"
+ "&redirect_uri=com.oktapreview.dev-999999:/callback"
+$"&client_id={OktaConfiguration.ClientId}"
+$"&state={state}"
+$"&code_challenge={codeChallenge}"
+ "&code_challenge_method=S256"
+$"&nonce={nonce}";
return url;
}
private string CreateCryptoGuid()
{
using (var generator = RandomNumberGenerator.Create())
{
var bytes = new byte[16];
generator.GetBytes(bytes);
return new Guid(bytes).ToString("N");
}
}
private string CreateCodeChallenge()
{
codeChallenge = GenerateCodeToVerify();
codeVerifier = codeChallenge;
using (var sha256 = SHA256.Create())
{
var codeChallengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeChallenge));
return Convert.ToBase64String(codeChallengeBytes);
}
}
private string GenerateCodeToVerify()
{
var str = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
Random rnd = new Random();
for (var i = 0; i < 100; i++)
{
str += possible.Substring(rnd.Next(0,possible.Length-1),1);
}
return str;
}
'''
After much online research, I discovered the issue was with how I was doing my post to get the token. This is how I made it work:
public static Dictionary<string, string> JsonDecode(string encodedString)
{
var inputs = new Dictionary<string, string>();
var json = JValue.Parse(encodedString) as JObject;
foreach (KeyValuePair<string, JToken> kv in json)
{
if (kv.Value is JValue v)
{
if (v.Type != JTokenType.String)
inputs[kv.Key] = v.ToString();
else
inputs[kv.Key] = (string)v;
}
}
return inputs;
}
private async Task<string> ExchangeAuthCodeForToken(string authCode)
{
string accessToken = string.Empty;
List<KeyValuePair<string, string>> kvdata = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("code", authCode),
new KeyValuePair<string, string>("redirect_uri", OktaConfiguration.Callback),
new KeyValuePair<string, string>("client_id", OktaConfiguration.ClientId),
new KeyValuePair<string, string>("code_verifier", codeVerifier)
};
var content = new FormUrlEncodedContent(kvdata);
var request = new HttpRequestMessage(HttpMethod.Post, OktaConfiguration.TokenUrl)
{Content = content, Method = HttpMethod.Post};
HttpClient client = new HttpClient();
HttpResponseMessage response = await client.SendAsync(request);
string text = await response.Content.ReadAsStringAsync();
Dictionary<string, string> data = JsonDecode(text);
data.TryGetValue("access_token", out accessToken);
return accessToken;
}

Get id of last Rest API POST using Entity Framework

I need to be able to access the id of a new Post. I will be using this id to populate another field called LocationId like this: "L" + id = LocationId (example L22) where 22 is the id of the new Post. Here is the code for my Post request:
private async void BtnSubmit_Clicked(object sender, EventArgs e)
{
var imageArray = FilesHelper.ReadFully(file.GetStream());
file.Dispose();
var location = new Models.Location()
{
LocationName = EntName.Text,
ImageArray = imageArray,
};
ApiServices apiServices = new ApiServices();
bool response = await apiServices.PostLocation(location);
bool response2 = await apiServices.InputLocationId(id, location);
if (!response || !response2)
{
await DisplayAlert("Alert", "Something wrong", "Cancel");
}
else
{
await DisplayAlert("Hi", "Your record has beed added successfully", "Alright");
}
await Navigation.PushAsync(new SetupPage());
This is on the client side. I have all the APIs created (such as PostLocation and InputLocationId)on Azure SQL Server. This is for a mobile inventory app built using Xamarin.
public async Task<bool> PostLocation(Location location)
{
var json = JsonConvert.SerializeObject(location);
var httpClient = new HttpClient();
var content = new StringContent(json, Encoding.UTF8, "application/json");
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", Settings.AccessToken);
var wimsApiUrl = "http://xxxxxxx.azurewebsites.net/api/Locations";
//Get the Body of the Post
var body = await httpClient.PostAsync(wimsApiUrl, content);
//Convert it to a string
var jString = await body.Content.ReadAsStringAsync();
//Place it in a JSON Object
JObject joResponse = JObject.Parse(jString);
//Parse the JSON Object into an Int from a String
var id = int.Parse(joResponse["Id"].ToString());
//This is used in my other script to Put the LocationId of Lxx
AddNewLocationPage.NewLocationId = id;
return body.IsSuccessStatusCode;
}
My Post Location API:
// POST: api/Locations
[ResponseType(typeof(Location))]
public IHttpActionResult PostLocation([FromBody] Location location)
{
string userId = User.Identity.GetUserId();
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var stream = new MemoryStream(location.ImageArray);
var guid = Guid.NewGuid().ToString();
var file = String.Format("{0}.jpg", guid);
var folder = "~/Content/Images";
var fullPath = String.Format("{0}/{1}", folder, file);
var response = FilesHelper.UploadPhoto(stream, folder, file);
if (response)
{
location.ImagePath = fullPath;
}
var newLocation = new Location()
{
LocationName = location.LocationName,
User = userId,
ImagePath = location.ImagePath
};
db.Locations.Add(newLocation);
db.SaveChanges();
return Ok(new { newLocation.Id});
}
I will then take the id and put it in this Put Request to create the LocationId:
public async Task<bool> InputLocationId(int id, Location location)
{
var json = JsonConvert.SerializeObject(location);
var httpClient = new HttpClient();
var content = new StringContent(json, Encoding.UTF8, "application/json");
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", Settings.AccessToken);
var wimsApiUrl = "http://xxxxxxx.azurewebsites.net/api/Locations/InputLocationId/";
var completeUrl = String.Format("{0}{1}", wimsApiUrl, id);
var response = await httpClient.PutAsync(completeUrl, content);
return response.IsSuccessStatusCode;
}
The InputLocationId API will automatically create the LocationId. Here is my API:
// PUT: api/Locations/5
[HttpPut]
[ResponseType(typeof(void))]
[Route("api/Locations/InputLocationId/{id}")]
public IHttpActionResult InputLocationId(int id, [FromBody] Location location)
{
//string userId = User.Identity.GetUserId();
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var result = db.Locations.FirstOrDefault(locationId => locationId.Id == id);
var resultant = String.Format("L{0}", id);
location.LocationName = location.LocationName;
result.LocationId = resultant;
db.SaveChanges();
return Ok("The record has been updated");
}
I am simply stuck on how to access that id!
// get the response body
var body = await httpClient.PostAsync(wimsApiUrl, content);
// load it into a JSON object using Newtonsoft
JObject data = JObject.Parse(body);
// get the id
var id = int.Parse(data["id"]);
The returns need to be converted into a string from the HttpResponseMessage.
var body = await httpClient.PostAsync(wimsApiUrl, content);
var jString = await body.Content.ReadAsStringAsync();
Then we can place it into a JSON Object:
JObject joResponse = JObject.Parse(jString);
Now this JSON Object can be parsed into an Int. Note it needs to be converted to a string.
var id = int.Parse(joResponse["Id"].ToString());

Facebook login in asp.net core not working and return 500

I have been using facebook login for one of my asp.net core projects. However, it stopped working for Facebook login suddenly. I am getting HTTP 500 error.
The issue is, even in the debug more, asp.net core is not mentioning any error. It is just same 500 error code. Nothing else.
When I tried to set breakpoint in the first line of public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null) function, I realized that it is not even hitting that and failing even before that.
I am not sure how to debug this further. Am I missing anything here? Or is there any change from FB in the login side?
The return URL being hit is by Facebook is:
https://localhost:44300/signin-facebook?code=AQBxGGw7ZCoa9xtXc3CCsVGRD9TJLL428bZ_eJpUu4CtVu3K4UrfOZuYYdwFBXzGZ6GOGXpOi2Nme_jfbewB84otVZhKZfs4i7Dhi9Y3E_rloU9ouLeIvuOsm29jr7IDCtTj_HM7rKuKjj3zmc4yz5i_fniZ9ZhMfXtSus5KyKa4EFkZTsmKrz2ngMlGQalUAob_52GJNhvSIXDlmiNSrZLJV3m7Zbkf9eXETQkqhu2L1kgXPvWkMzVP8EN00GwRCYB3xT1kQMOimDANRKhziZjoVS5QZFUJTP0Faj47tE1xNfmAzb30iuwcaRORCOTMipUrnRvOO4nGRo8JuUNdPJaO&state=CfDJ8EHIO3qHMHFClr5BAt4EC1Wj7LyAs5Pg1XOqKo4uFiJM2Jr1rNyooxLIu2fbXr6Z3X5_kqbF_7WwFfvF3L3H4xgyooo-3Y9BV8Zh1S5wXlLJDAyCT5_LwkPJ1j8Zrwx4umQJp6NOl76GwRXpi1_BHlWGRxnh_naTL35iqeGovOa8oEDC0jOQ4trRe7YG3fV_ptjWk4yOnvJnsI81O-6wfyhdc3jm-LTP7ZO7-duf_lPZXZ8mL42XyLXDTIyOJ__S2yLYdvwItdDVntsM8Hwq94goXdU-RaH7ZkDA8iAzeCl3Ke0tWAdYBKy9vooJIXmE9Q#_=_
Based on this article, it should have state_token too in the URL. But that seems to be missing here. How can I figure out here what is the actual error?
I am using asp.net core RC2 release.
My callback function is:
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null)
{
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
return RedirectToAction(nameof(Login));
}
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
if (result.Succeeded)
{
_logger.LogInformation(5, "User logged in with {Name} provider.", info.LoginProvider);
return RedirectToLocal(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl });
}
if (result.IsLockedOut)
{
return View("Lockout");
}
else
{
// If the user does not have an account, then ask the user to create an account.
ViewData["ReturnUrl"] = returnUrl;
ViewData["LoginProvider"] = info.LoginProvider;
var email = info.ExternalPrincipal.FindFirstValue(ClaimTypes.Email);
if (email == null)
{
return View("Error");
}
/* Determine user from external login info */
var name = info.ExternalPrincipal.FindFirstValue(ClaimTypes.Name);
string firstName;
string lastName = "";
if (!string.IsNullOrWhiteSpace(name))
{
firstName = name.Split(' ').Length > 1? name.Split(new[] { ' ' }, 2)[0] : name;
lastName = name.Split(' ').Length > 1 ? name.Split(new[] { ' ' }, 2)[1] : "";
}
else
firstName = email.Split('#')[0];
var user = await _userManager.FindByEmailAsync(email);
if (user == null)
{
/* No user with same email ID. So, create a new user.*/
var newUser = new ApplicationUser
{
UserName = email,
Email = email,
FirstName = firstName,
LastName = lastName,
PasswordLastModifiedTime = DateTime.UtcNow,
UserSignUpDate = DateTime.UtcNow
};
var userCreationResult = await _userManager.CreateAsync(newUser);
if (userCreationResult.Succeeded)
{
userCreationResult = await _userManager.AddLoginAsync(newUser, info);
if (userCreationResult.Succeeded)
{
// Add user claims TODO:// Test if the claims are added successfully.
await _userManager.AddClaimAsync(newUser, new Claim("FirstName", newUser.FirstName));
await _userManager.AddClaimAsync(newUser, new Claim("LastName", newUser.LastName));
// Set user email to confirmed. This is more of work around
var code = await _userManager.GenerateEmailConfirmationTokenAsync(newUser);
userCreationResult = await _userManager.ConfirmEmailAsync(newUser, code);
if (userCreationResult.Succeeded)
{
//Create Subscription for user
var planService = new PlanServices();
var plan = planService.Find((int)SubscriptionType.Basic);
await _subscriptionService.CreateSubscription(newUser, plan, null);
await _signInManager.SignInAsync(newUser, isPersistent: false);
_logger.LogInformation(6, "User created an account using {Name} provider.",
info.LoginProvider);
await _emailSender.SendWelcomeEmailAsync(newUser.Email, newUser.FirstName);
return RedirectToLocal(returnUrl);
}
}
}
}
else
{
/* A user with email ID exists. Associate the account with that.*/
var loginAddResult = await _userManager.AddLoginAsync(user, info);
if (loginAddResult.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
return RedirectToLocal(returnUrl);
}
}
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email, FirstName = firstName, LastName = lastName});
}
}
And ConfigureServices method is:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
services.AddScoped<ApplicationDbContext>();
services.AddIdentity<ApplicationUser, IdentityRole>(o =>
{
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
o.Password.RequireNonLetterOrDigit = false;
o.Password.RequiredLength = 8;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddCaching();
services.AddSession();
services.AddMvc();
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
services.AddTransient<ISubscriptionService, SubscriptionService>();
services.Configure<AuthMessageSenderOptions>(Configuration);
services.Configure<RecaptchaOptions>(Configuration);
__serviceProvider = services.BuildServiceProvider();
}
The thing is, it worked well for a long time and has stopped working now. Also, it is not even hitting ExternalLoginCallback, so I am not sure where to head for debugging it further.

ASP MVC RouteConfig with authentication return error repository not found

I tried to make an authorization in my asp with my git client, so my git client will be requested an authorization from my server. When i tried to send a request to my git client, it was showing an error
repository http://localhost/git/user/try.git/info/ref not found
Here is my routeconfig
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
#region remoteURL
routes.MapRoute(
name: "RemoteURL",
url: "git/{project}.git/{*verb}",
defaults: new { controller = "Git", action = "Smart" }
);
routes.MapRoute(
name: "Git",
url: "git/{project}/{*verb}",
defaults: new { controller = "Git", action = "Smart" }
);
#endregion
#region Account;
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
#endregion;
}
and this is my controller that use an attribute :
public class GitController : Controller
{
[SmartGit]
public ActionResult Smart(string project, string service, string verb)
{
switch (verb)
{
case "info/refs":
return InfoRefs(project, service);
case "git-upload-pack":
return ExecutePack(project, "git-upload-pack");
case "git-receive-pack":
return ExecutePack(project, "git-receive-pack");
default:
return RedirectToAction("Tree", "Repository", new { Name = project });
}
}
and then this is my attribute smartgit
public class SmartGitAttribute : SmartAuthorizeAttribute
{
private const string AuthKey = "GitCodeGitAuthorize";
private GitCodeContext db = new GitCodeContext();
private string project;
private string verb;
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
var right = false;
var userfound = false;
List<string> paramParsing = new List<string>();
//url.Split("")
//base.OnAuthorization(filterContext);
var controller = filterContext.Controller as GitController;
if (controller == null)
return;
// git.exe not accept cookies as well as no session available
var auth = controller.HttpContext.Request.Headers["Authorization"];
if (!String.IsNullOrEmpty(auth))
{
var bytes = Convert.FromBase64String(auth.Substring(6));
var certificate = Encoding.ASCII.GetString(bytes);
var index = certificate.IndexOf(':');
var password = certificate.Substring(index + 1);
var username = certificate.Substring(0, index);
//var user = controller.MembershipService.Login(username, password);
if (WebSecurity.Login(username, password))
{
WebSecurity.Login(username, password);
userfound = true;
}
}
var projectField = controller.ValueProvider.GetValue("project");
var serviceField = controller.ValueProvider.GetValue("service");
var verbField = controller.ValueProvider.GetValue("service");
//filterContext.Controller.ValueProvider
var project = projectField == null ? null : projectField.AttemptedValue;
var service = serviceField == null ? null : serviceField.AttemptedValue;
var verb = verbField == null ? null : serviceField.AttemptedValue;
if (string.IsNullOrEmpty(service) && userfound) // redirect to git browser
{
right = true;
}
else if (string.Equals(service, "git-receive-pack", StringComparison.OrdinalIgnoreCase) && userfound) // git push
{
//right = controller.RepositoryService.CanWriteRepository(project, username);
right = true;
}
else if (string.Equals(service, "git-upload-pack", StringComparison.OrdinalIgnoreCase) && userfound ) // git fetch
{
//right = controller.RepositoryService.CanReadRepository(project, username);
right = true;
}
if (!userfound)
{
if (WebSecurity.CurrentUserName == "")
{
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.AddHeader("WWW-Authenticate", "Basic realm=\"coba\"");
filterContext.Result = new HttpUnauthorizedResult();
}
else
{
throw new UnauthorizedAccessException();
}
}
}
I found my own mistake, maybe my response doesn't have enough information so i decide to add a few information in my SmartGitAttribute
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusDescription = "Unauthorized";
filterContext.HttpContext.Response.AddHeader("WWW-Authenticate", "Basic realm=\"Secure Area\"");
filterContext.HttpContext.Response.Write("401, please authenticate");
filterContext.HttpContext.Response.StatusCode = 401;
filterContext.Result = new EmptyResult();
filterContext.HttpContext.Response.End();
this is reference that can help you to solve response authentication