I'm really baffled right now. So I've been working on a Web API and unit testing the project as I'm going and just made a change with a lot of controllers recently but completely left the AccountController untouched. In my AccountController I have the function Register() which has been working great for the past 2 months everytime I test. Now all of a sudden though when I enter Register and call,
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
My program hangs and won't come back. I tested some things out and this only happens when called from my unit test (Which also hasn't changed) and when I call Register from Postman with the exact same JSON it works just fine.
Register API Function
// POST api/Account
[AllowAnonymous]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var user = new ApplicationUser() { UserName = model.UserName, Email = model.Email, Active = true, CreationDate = DateTime.Now };
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
Test Function
[TestMethod]
public void Register_Pass()
{
// Arrange & Act
var db = new WizardSwearsDB();
new DBCleanup().RemoveRegisteredUser(userName);
string apiCall = "";
new
{
UserName = userName,
Email = email,
Password = password,
ConfirmPassword = password
}.Post(baseUrl + apiCall);
var user = (from u in db.AspNetUsers
where u.UserName == userName
select u).FirstOrDefault();
// Assert
Assert.IsNotNull(user);
Assert.AreEqual(userName, user.UserName);
}
JSON Post Functionality
public static void Post(this object obj, string url, string securityToken = null)
{
var request = WebRequest.Create(url);
obj.Request<string>(url, postJsonRequestBuilder, null, securityToken);
}
private static Func<string, object, string, WebRequest> postJsonRequestBuilder = (url, o, st) => jsonRequestBuilder(url, o, "POST", st);
private static Func<string, object, string, string, WebRequest> jsonRequestBuilder = delegate(string url, object obj, string method, string securityToken)
{
WebRequest request = WebRequest.Create(url);
if (!string.IsNullOrEmpty(securityToken)) request.Headers.Add("Authorization", string.Format("Bearer {0}", securityToken));
request.Method = method;
if (obj != null)
{
request.ContentType = "application/json";
var payload = encoding.GetBytes(JsonConvert.SerializeObject(obj));
request.ContentLength = payload.Length;
using (var stream = request.GetRequestStream())
{
stream.Write(payload, 0, payload.Length);
}
}
return request;
};
Remove Registered User Function and Remove Employee Function
public void RemoveRegisteredUser(string userName)
{
// Open up a connection to the database
var db = new WizardSwearsDB();
// Get the user from the ASPNetUsers table
var dbUserASP = (from b in db.AspNetUsers
where b.UserName == userName
select b).FirstOrDefault();
// Remove the user from both tables and save changes
if (dbUserASP != null)
{
db.AspNetUsers.Remove(dbUserASP);
db.SaveChanges();
}
RemoveEmployee(userName);
}
public void RemoveEmployee(string userName)
{
// Open up a connection to the database
var db = new WizardSwearsDB();
// Get the user from the ASPNetUsers table
var employee = (from b in db.Employees
where b.UserName == userName
select b).FirstOrDefault();
// Remove the user from both tables and save changes
if (employee != null)
{
db.Employees.Remove(employee);
db.SaveChanges();
}
}
So none of these functions have changed at all, and my guess is it has to do something with await, but I just can't figure out why it is behaving this way.
Just solved the problem. Did a full restart of my computer and Visual Studio and voila. On a side note, anyone familiar with why this fixed the issue? Maybe just a Visual Studio bug?
I had the same issue arising from trying to create a user with an existing username - no error, just nothing, blank.
Related
I am using InstagramApiSharp
Here is my code :
public static async void CreateAccount()
{
var _instaApi = InstaApiBuilder.CreateBuilder().Build();
var email = "ramtinaaka#live.com";
var username = "rmt40306";
var password = "rmt122345678";
var firstName = "Ramtiinnn";
var checkEmail = await _instaApi.CheckEmailAsync(email);
if(checkEmail.Succeeded && checkEmail.Value.Available)
{
var create = await _instaApi.CreateNewAccountAsync(username, password, email, firstName);
if (create.Succeeded)
{
Console.WriteLine("Success");
return;
}
Console.WriteLine("Error");
}
return;
}
I get Error printed on console when I call CreateAccount method as well as no account created.
Create Account Wiki
I believe I gave all needed info, I don't think there is anything else to add.
This API is intended for a mobile application. The goal is to let the user confirm the email upon registration. When the user registers, a confirmation link is generated and sent over the email. I've done it the same way in a MVC project, it worked fine, but in a Web API project looks like it ain't gonna cut.
Now when the user clicks that link, the respective action method should be hit and do the job.
The only problem is, the ConfirmEmail action method is just not getting triggered when clicking the confirmation link although it looked fine.
Here are the main configurations which might help
MVC service configuration
services.AddMvc(options =>
{
options.EnableEndpointRouting = true;
options.Filters.Add<ValidationFilter>();
})
.AddFluentValidation(mvcConfiguration => mvcConfiguration.RegisterValidatorsFromAssemblyContaining<Startup>())
.SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_3_0);
Identity Service
public async Task<AuthenticationResult> RegisterAsync(string email, string password)
{
var existingUser = await _userManager.FindByEmailAsync(email);
if(existingUser != null)
{
return new AuthenticationResult { Errors = new[] { "User with this email address exists" } };
}
// generate user
var newUser = new AppUser
{
Email = email,
UserName = email
};
// register user in system
var result = await _userManager.CreateAsync(newUser, password);
if (!result.Succeeded)
{
return new AuthenticationResult
{
Errors = result.Errors.Select(x => x.Description)
};
}
// when registering user, assign him user role, also need to be added in the JWT!!!
await _userManager.AddToRoleAsync(newUser, "User");
// force user to confirm email, generate token
var token = await _userManager.GenerateEmailConfirmationTokenAsync(newUser);
// generate url
var confirmationLink = _urlHelper.Action("ConfirmEmail", "IdentityController",
new { userId = newUser.Id, token = token }, _httpRequest.HttpContext.Request.Scheme);
// send it per email
var mailresult =
await _emailService.SendEmail(newUser.Email, "BingoApp Email Confirmation",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(confirmationLink)}'>clicking here</a>.");
if (mailresult)
return new AuthenticationResult { Success = true };
else
return new AuthenticationResult { Success = false, Errors = new List<string> { "Invalid Email Address"} };
}
Controller
[HttpPost(ApiRoutes.Identity.Register)]
public async Task<IActionResult> Register([FromBody] UserRegistrationRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest(new AuthFailedResponse
{
Errors = ModelState.Values.SelectMany(x => x.Errors.Select(xx => xx.ErrorMessage))
});
}
// register the incoming user data with identity service
var authResponse = await _identityService.RegisterAsync(request.Email, request.Password);
if (!authResponse.Success)
{
return BadRequest(new AuthFailedResponse
{
Errors = authResponse.Errors
});
}
// confirm registration
return Ok();
}
[HttpGet]
public async Task<IActionResult> ConfirmEmail(string userId, string token)
{
if (userId == null || token == null)
{
return null;
}
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
return null;
}
var result = await _userManager.ConfirmEmailAsync(user, token);
if (result.Succeeded)
{
await _emailService.SendEmail(user.Email, "BingoApp - Successfully Registered", "Congratulations,\n You have successfully activated your account!\n " +
"Welcome to the dark side.");
}
return null;
}
Your _urlHelper.Action(..) looks a bit suspicious to me.
I'm not sure you should pass the full controller name, that is, including the actual word controller.
Try _urlHelper.Action("ConfirmEmail", "Identity", instead.
As a tip: I try to avoid magic strings like these by using nameof(IdentityController) because it will return the controller name without the controller postfix.
I'm using latest Stripe.net version
await SubscriptionsFacade.SubscribeUserAsync(user, planId, taxPercent: taxPercent);
raises
[MissingMethodException: Method not found: 'Void Stripe.StripeCustomerCreateOptions.set_Card(Stripe.StripeCreditCardOptions)'.]
Has something changed? I updated to the latest version and now my Stripe.net app is broken. Did Stripe introduce a new way of creating cards?
Here's the full code:
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var userIP = GeoLocation.GetUserIP(Request).Split(':').First();
var user = new ApplicationUser
{
UserName = model.Email,
Email = model.Email,
RegistrationDate = DateTime.UtcNow,
LastLoginTime = DateTime.UtcNow,
IPAddress = userIP,
IPAddressCountry = GeoLocationHelper.GetCountryFromIP(userIP),
BillingAddress = new BillingAddress()
};
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// Create Stripe user
var taxPercent = user.IPAddressCountry != null && EuropeanVat.Countries.ContainsKey(user.IPAddressCountry) ?
EuropeanVat.Countries[user.IPAddressCountry] : 0;
// if no plan set, default to professional
var planId = string.IsNullOrEmpty(model.SubscriptionPlan)
? "starter"
: model.SubscriptionPlan;
var customer = new StripeCustomerService();
var customerInfo = customer.Get(user.CustomerIdentifier);
await SubscriptionsFacade.SubscribeUserAsync(user, planId, taxPercent: taxPercent);
await UserManager.UpdateAsync(user);
await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
await UserManager.EmailService.SendWelcomeEmail(user.UserName, user.Email);
TempData["flash"] = new FlashSuccessViewModel("Congratulations! Your account has been created.");
return RedirectToAction("Index", "Notes");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
and SubscribeUserAsync:
https://github.com/pedropaf/saas-ecom/blob/1370ac169807e97ffb7414610d5be4de4a3cc9ae/SaasEcom.Core/Infrastructure/Facades/SubscriptionsFacade.cs
as far as I can tell SubscribeUserAsync is requiring a card with its call.
private async Task<Subscription> SubscribeUserAsync
(SaasEcomUser user, string planId, CreditCard creditCard, int trialInDays = 0, decimal taxPercent = 0)
or
public async Task<Subscription> SubscribeUserAsync
(SaasEcomUser user, string planId, decimal taxPercent = 0, CreditCard creditCard = null)
since you are subscribing a user it probably wants a credit card to go with it. I would add either a card via creating a token or via calling and existing one with
var customer = new StripeCustomerService();
var customerInfo = customer.Get(user.CustomerIdentifier);
//then store card with customerInfo.DefaultSourceId somewhere and use it
I'm trying to decrypt the password on the login method, but it allows to login with any password I type in, not sure why, maybe someone could help me out?
My login method in the db layer:
public string loginUser(string userName, string pass)
{
string result = "";
try
{
var mongoClient = new MongoClient("mongodb://localhost");
var database = mongoClient.GetDatabase("SearchForKnowledge");
var coll = database.GetCollection<BsonDocument>("Users");
var filter = Builders<BsonDocument>.Filter.Eq("userName", userName);
var results = coll.Find(filter).ToList().First();
if (BCrypt.Net.BCrypt.Verify(pass, results["password"].ToString()))
{
result = results["userName"].ToString();
}
}
catch (Exception ex)
{
result = "";
}
return result;
}
My user controller:
public ActionResult Login(UsersLogin form)
{
User user = new User();
UserDB udb = new UserDB();
if (!form.Username.IsEmpty())
{
udb.loginUser(form.Username, form.Password);
Session["userName"] = form.Username;
return RedirectToRoute("Home");
}
return RedirectToRoute("Login");
}
The problem is in your controller
udb.loginUser(form.Username, form.Password);
Session["userName"] = form.Username;
return RedirectToRoute("Home");
You call udb.loginUser(form.Username, form.Password);, but you never check the return value of udb.loginUser method, so your code will always redirect to the home page no matter what the user name and password are.
Based on the code of loginUser method, it will return an empty string if the login fails, so change the above three lines of code to below
var loginResult = udb.loginUser(form.Username, form.Password);
if (!string.IsNullOrEmpty(loginResult))
{
Session["userName"] = form.Username;
return RedirectToRoute("Home");
}
I need some help regarding mvc 5 using the google login provider and getting some youtube data. right now i think i get things a little mixed up. i'm not new to mvc but to version 5's owin middleware features. well, and not experienced in implementing oauth 2.0.
What i want:
Login to my MVC5 Application via Google.
Read some Youtube information from the logged in user.
What i have done so far:
Followed this Google OAuth 2.0 tutorial: Web applications (ASP.NET MVC).
Installed Google.Apis.Auth.MVC via NuGet.
Implemented AppFlowMetadata and AuthCallbackController as described.
Configured the redirect uri to "/AuthCallback/IndexAsync" as described.
Implemented a YoutubeController with the following action just to dump out some data:
public async Task<ActionResult> IndexAsync()
{
var result =
await new AuthorizationCodeMvcApp(this, new AppFlowMetadata())
.AuthorizeAsync(cancellationToken);
if (result.Credential == null)
{
return new RedirectResult(result.RedirectUri);
}
else
{
var service = new YouTubeService(new BaseClientService.Initializer
{
HttpClientInitializer = result.Credential,
ApplicationName = "MyYoutubeApplication"
});
var playlists = service.Playlists.List("contentDetails, snippet");
playlists.Mine = true;
var list = await playlists.ExecuteAsync();
var json = new JavaScriptSerializer().Serialize(list);
ViewBag.Message = json;
return View();
}
}
So what this does, when trying to access /Youtube/IndexAsync is redirecting me to google, asking for my credentials.
when entered, i'm asked if i'm ok with the permission asked by the application. after confirming, i get redirected to my page, showing my /Youtube/IndexAsync page with the requested data. so far so good, but that's not quite what i want.
what (i think) i have done here is that i completely bypassed the asp.net identity system. the user is not logged in to my application let alone registered.
i want the user to log in with google, register in my application and provide access to his youtube data. then, when on a specific page, retrieve data from the user's youtube account.
What i also have tried:
Following this ASP.Net MVC5 Tutorial
This tutorial does not mention the NuGet package "Google.Apis.Auth.MVC" and talks something about a magic "/signin-google" redirect uri".
This also works, but breaks the solution above, complaining about a wrong redirect uri.
When using this approach, it seems not right to me call AuthorizeAsync in YoutubeController again, since i should already be authorized.
So i'm looking for some light in the dark, telling me what i'm mixing all together :) I hope the question is not as confused as i am right now.
I managed to do this using GooglePlus, haven't tried Google. Here's what I did:
Install the nugets:
> Install-Package Owin.Security.Providers
> Install-Package Google.Apis.Youtube.v3
Add this to Startup.auth.cs:
var g = new GooglePlusAuthenticationOptions();
g.ClientId = Constants.GoogleClientId;
g.ClientSecret = Constants.GoogleClientSecret;
g.RequestOfflineAccess = true; // for refresh token
g.Provider = new GooglePlusAuthenticationProvider
{
OnAuthenticated = context =>
{
context.Identity.AddClaim(new Claim(Constants.GoogleAccessToken, context.AccessToken));
if (!String.IsNullOrEmpty(context.RefreshToken))
{
context.Identity.AddClaim(new Claim(Constants.GoogleRefreshToken, context.RefreshToken));
}
return Task.FromResult<object>(null);
}
};
g.Scope.Add(Google.Apis.YouTube.v3.YouTubeService.Scope.YoutubeReadonly);
g.SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie;
app.UseGooglePlusAuthentication(g);
The above code does two things:
Enable authentication via. Google+
Requests for the access token and the refresh token. The tokens are then added as a claim in the GooglePlus middleware.
Create a method that will store the claims containing the token to the database. I have this in the AccountController.cs file
private async Task StoreGooglePlusAuthToken(ApplicationUser user)
{
var claimsIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
if (claimsIdentity != null)
{
// Retrieve the existing claims for the user and add the google plus access token
var currentClaims = await UserManager.GetClaimsAsync(user.Id);
var ci = claimsIdentity.FindAll(Constants.GoogleAccessToken);
if (ci != null && ci.Count() != 0)
{
var accessToken = ci.First();
if (currentClaims.Count() <= 0)
{
await UserManager.AddClaimAsync(user.Id, accessToken);
}
}
ci = claimsIdentity.FindAll(Constants.GoogleRefreshToken);
if (ci != null && ci.Count() != 0)
{
var refreshToken = ci.First();
if (currentClaims.Count() <= 1)
{
await UserManager.AddClaimAsync(user.Id, refreshToken);
}
}
}
You'll need to call it in 2 places in the AccountController.cs: Once in ExternalLoginCallback:
case SignInStatus.Success:
var currentUser = await UserManager.FindAsync(loginInfo.Login);
if (currentUser != null)
{
await StoreGooglePlusAuthToken(currentUser);
}
return RedirectToLocal(returnUrl);
and once in ExternalLoginConfirmation:
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user);
if (result.Succeeded)
{
result = await UserManager.AddLoginAsync(user.Id, info.Login);
if (result.Succeeded)
{
await StoreGooglePlusAuthToken(user);
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
return RedirectToLocal(returnUrl);
}
}
Now that we've got the users access token and refresh token we can use this to authenticate the user.
I tried a simple search I saw in the examples and it worked:
private async Task<Models.YouTubeViewModel> Search(string searchTerm)
{
var user = (ClaimsPrincipal)Thread.CurrentPrincipal;
var at = user.Claims.FirstOrDefault(x => x.Type == Constants.GoogleAccessToken);
var rt = user.Claims.FirstOrDefault(x => x.Type == Constants.GoogleRefreshToken);
if (at == null || rt == null)
throw new HttpUnhandledException("Access / Refresh Token missing");
TokenResponse token = new TokenResponse
{
AccessToken = at.Value,
RefreshToken = rt.Value
};
var cred = new UserCredential(new GoogleAuthorizationCodeFlow(
new GoogleAuthorizationCodeFlow.Initializer()
{
ClientSecrets = new ClientSecrets()
{
ClientId = Constants.GoogleClientId,
ClientSecret = Constants.GoogleClientSecret
}
}
),
User.Identity.GetApplicationUser().UserName,
token
);
var youtubeService = new YouTubeService(new BaseClientService.Initializer()
{
ApplicationName = this.GetType().ToString(),
HttpClientInitializer = cred,
});
var searchListRequest = youtubeService.Search.List("snippet");
searchListRequest.Q = searchTerm;
searchListRequest.MaxResults = 50;
// Call the search.list method to retrieve results matching the specified query term.
var searchListResponse = await searchListRequest.ExecuteAsync();
Models.YouTubeViewModel vm = new Models.YouTubeViewModel(searchTerm);
foreach (var searchResult in searchListResponse.Items)
{
switch (searchResult.Id.Kind)
{
case "youtube#video":
vm.Videos.Add(new Models.Result(searchResult.Snippet.Title, searchResult.Id.VideoId));
break;
case "youtube#channel":
vm.Channels.Add(new Models.Result(searchResult.Snippet.Title, searchResult.Id.ChannelId));
break;
case "youtube#playlist":
vm.Playlists.Add(new Models.Result(searchResult.Snippet.Title, searchResult.Id.PlaylistId));
break;
}
}
return vm;
}
Model Classes
public class Result
{
public string Title { get; set; }
public string Id { get; set; }
public Result() { }
public Result(string title, string id)
{
this.Title = title;
this.Id = id;
}
}
public class YouTubeViewModel
{
public string SearchTerm { get; set; }
public List<Result> Videos { get; set; }
public List<Result> Playlists { get; set; }
public List<Result> Channels { get; set; }
public YouTubeViewModel()
{
Videos = new List<Result>();
Playlists = new List<Result>();
Channels = new List<Result>();
}
public YouTubeViewModel(string searchTerm)
:this()
{
SearchTerm = searchTerm;
}
}
Reference: http://blogs.msdn.com/b/webdev/archive/2013/10/16/get-more-information-from-social-providers-used-in-the-vs-2013-project-templates.aspx