ASP.NET Identity IUserEmailStore error in MVC - asp.net-mvc

I am trying to send a confirmation email when a user registers together with the generated Token. Token is OK. But when sending email,I am getting this error:
System.NotSupportedException: Store does not implement IUserEmailStore<TUser>.
And my code inside AccountController in the Register method looks like this: It is the SendEmailAsync() method which is causing the error:
if (result.Succeeded)
{
var code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(user.Id, "Confirm your account", "Please confirm your account by clicking this link: link");
ViewBag.Link = callbackUrl;
return View("DisplayEmail");
}
In my IdentityConfig.cs file, I have a class:
public class ApplicationUserManager : UserManager<ApplicationUser>
{
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{
}
}
Also inside my IdentityConfig.cs file, I have another class:
public class EmailService : IIdentityMessageService
{
public Task SendAsync(IdentityMessage message)
{
// Plug in your email service here to send an email.
SmtpClient client = new SmtpClient();
return client.SendMailAsync(ConfigurationManager.AppSettings["TranscriptEmailAddr"],
message.Destination,
message.Subject,
message.Body);
}
}
Please assist.

I just figured out that I had Microsoft.AspNet.Identity.EntityFramework v1.0.0 installed but I needed v2.x. I installed it through the Package Manager console by entering:
Install-Package Microsoft.AspNet.Identity.EntityFramework -Version 2.2.1
In this later version, UserStore implements IUserEmailStore which has the new email methods.

In your model, or somewhere in your code you must have a UserStore class (unless you use a framework like EntityFrameworkIdentity).
Try to find it by searching the entire solution for UserStore and you'll see that you have to implement this interface on it.
like:
public class UserStore : IUserStore<UserIdentity>, IUserPasswordStore<UserIdentity>, IUserEmailStore<UserIdentity>{
}

Related

Blazor Server GraphServiceClient not authenticated in wrapper library

I have a Blazor Server application that authenticates users through AAD. I'm using the Microsoft Graph SDK to query user info such as photo, etc., which I can do using DI to get the GraphServiceClient directly within a component in the Blazor Server app.
[Inject] private GraphServiceClient GraphServiceClient { get; set; }
However, for testing purposes, I've created a wrapper around the GraphServiceClient called IGraphService in another library within the same solution. This is where the problem occurs and the GraphServiceClient fails authentication with:
No account or login hint was passed to the AcquireTokenSilent call
My code is set up as follows:
Program.cs
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi(builder.Configuration.GetValue<string>("Graph:Scopes")?.Split(' '))
.AddMicrosoftGraph(builder.Configuration.GetSection("Graph"))
.AddInMemoryTokenCaches();
builder.Services.AddScoped<IGraphService, GraphService>();
appsettings.json
"AzureAd": {
"Instance": "https://login.microsoftonline.com",
"Domain": "{domain}.onmicrosoft.com",
"CallbackPath": "/signin-oidc",
"TenantId": "{tenantId}",
"ClientId": "{clientId}",
"ClientSecret": "{clientSecret}"
},
"Graph": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"Scopes": "user.read user.readbasic.all"
},
The part of the implementation of the GraphService I created to wrap the GraphServiceClient that tries to set up the GraphServiceClient:
return new GraphServiceClient(new DelegateAuthenticationProvider((requestMessage) =>
{
string[] scopes = new[] { "user.read", "user.readbasic.all" };
ITokenAcquisition tokenService = _contextAccessor.HttpContext.RequestServices.GetRequiredService<ITokenAcquisition>();
var token = tokenService.GetAccessTokenForUserAsync(scopes); <-- FAILS!!!
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Result);
return Task.FromResult(0);
}));
Clearing my cookies, this works fine the first time the app runs, but on restarts, it fails with the same message. Injecting the GraphServiceClient directly into a component works fine too, but I'm trying to wrap it so the components are loosely coupled.
Additionally, if I pass the AccessToken from the Blazor Server project to the wrapper library, it works fine too, but I want to avoid that.
What am I missing? Is my setup not right? Thanks for any help or pointers :)
Update 1
To simplify the issue, I have a component on a page and I can successfully request the user's token in that component, every time.
public sealed partial class HeaderBar
{
[Inject] private ITokenAcquisition TokenAcquisition { get; set; } = null!;
[Inject] private IGraphService GraphService { get; set; } = null!;
protected override async Task OnInitializedAsync()
{
try
{
// this works every time!
string token = await TokenAcquisition.GetAccessTokenForUserAsync(new[] { "user.read", "user.readbasic.all" });
var photo = await GraphService.GetUserPhoto();
...
}
catch (Exception ex)
{
...
}
}
}
As you can see, I'm not doing anything with the token, but if I remove that line, the same line within the GraphService fails. It will only succeed and get a token if I make the same call within the component first.
I've tried moving the code to the App.razor, but it fails there too.
Thanks to the following issue, I was able to pinpoint the problem and resolve it:
https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/issues/281
Problem:
When you lose the in-memory cache while your cookies are still alive, the auth calls are skipped and you don't bootstrap the token cache, which throws the MsalUIException described in the original question above. That explains why everything works the first time the app runs and doesn't when it gets restarted.
Some of the solutions describe using an attribute to re-populate the cache. However, in Blazor Server, there is no "Controller", so we can't use the AuthorizeForScopes attribute.
Solution:
I've created a page model for the standard _Host.csthml page that will attempt to get a token from the token cache, and if it's not populated, the AuthorizeForScopes attribute will do that for me (Thanks wmgdev!).
[AuthorizeForScopes(Scopes = new[] { "user.read", "user.readbasic.all" })]
public class _Host : PageModel
{
private readonly ITokenAcquisition _tokenAcquisition;
public _Host(ITokenAcquisition tokenAcquisition)
{
_tokenAcquisition = tokenAcquisition;
}
public async Task OnGetAsync()
{
// Get a token. If the token cache is not populated, the `AuthorizeForScopes` attribute will re-authorize, which will populate the cache
await _tokenAcquisition.GetAccessTokenForUserAsync(new[] { "user.read", "user.readbasic.all" });
}
}

How to use OAuth with Swagger and NSwagStudio

I'm trying to generate a C# client for an API that has provided a swagger.json file to me, located at this link;
https://api.ekm.net/swagger/v1/swagger.json
Using the NSwagStudo application I am able to import the configuration file and generate a file called Client.cs which implements a class called Client and it has methods on it that match the API.
However when I call any of the methods I get an "Unauthorized" exception and I can not find any way to provide the OAuth key and secret to the client or anyone doing similar with other authentication methods.
Inspecting the swagger configuration files does show that OAuth is indicated as the authentication method as follows;
"securityDefinitions": {
"OAuth": {
"flow": "accessCode",
"authorizationUrl": "https://api.ekm.net/connect/authorize",
"tokenUrl": "https://api.ekm.net/connect/token",
"scopes": {
"tempest.customers.read": "Read a shop's customers.",
"tempest.customers.write": "Modify a shop's customers.",
"tempest.orders.read": "Read a shops orders.",
"tempest.orders.write": "Modify a shop's orders.",
"tempest.products.read": "Read a shop's products.",
"tempest.products.write": "Modify a shop's products.",
"tempest.categories.read": "Read a shop's categories.",
"tempest.categories.write": "Modify a shop's categories.",
"tempest.settings.orderstatuses.read": "Read a shop's order statuses.",
"tempest.settings.domains.read": "Read a shop's domains."
},
"type": "oauth2",
"description": "In order to ensure the safety of our users data, we require all partner applications to register via the [Partner Dashboard](https://partners.ekm.net/). Once registered, partners are provided with an application key, which can be used during an OAuth2 handshake to create a token. This token can then used to make requests on behalf of a merchant."
}
},
My test code is as follows;
static void Main(string[] args)
{
var swagClient = new Client();
var ords = swagClient.ApiV1OrdersGetAsync(1, 100).Result; // This call throws SwaggerException: Unauthorized
}
The Client class does not have any obvious methods or properties to set the security values or any constructor parameters.
Does anyone have an example of how to achieve this?
I agree. It's kind of strange that it doesn't just accept some kind of "insert JWT here".
Anyway, this is how I've fixed it:
Inject HttpClient
Tick on the box named "Inject HttpClient via constructor" in NSwagStudio
CustomMessageHandler
Introduce a custom HttpMessageHandler:
internal class AuthTokenHttpMessageHandler: HttpClientHandler
{
private readonly Action<HttpRequestMessage, CancellationToken> _processRequest;
public AuthTokenHttpMessageHandler(Action<HttpRequestMessage, CancellationToken> processRequest)
{
_processRequest = processRequest;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
_processRequest(request, cancellationToken);
return base.SendAsync(request, cancellationToken);
}
}
This handler accepts a delegate in which you can provide your JWT.
Integrating with your client
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
public class MyService : IDisposable
{
private readonly AuthTokenHttpMessageHandler _messageHandler;
private readonly HttpClient _httpClient;
private readonly MyNSwagClient _client;
public MyService()
{
_messageHandler = new AuthTokenHttpMessageHandler((req, _) =>
{
req.Headers.Authorization = new AuthenticationHeaderValue("bearer", "your token goes here");
});
_httpClient = new HttpClient(_messageHandler);
_client = new MyNSwagClient(_httpClient);
}
public async Task<SomeModel> GetStuffAsync(string paramenter1)
{
return await _client.StuffGetAsync(parameter1);
}
public void Dispose()
{
_httpClient?.Dispose();
_messageHandler?.Dispose();
}
}
I hope this helps you

Version Deprecation Facebook Graph API v2.2

our Facebook Login is not working right now. We have received a message from Facebook Developer Portal:
"Name of app" currently has access to Graph API v2.2 which will reach the end of its
2-year lifetime on 27 March, 2017. To ensure a smooth transition,
please migrate all calls to Graph API v2.3 or higher.
To check if your app will be affected by this upgrade you can use the
Version Upgrade Tool. This will show you which calls, if any, are
affected by this change as well as any replacement calls in newer
versions. If you do not see any calls, your app may not be affected by
this change.
You can also use our changelog to see the full list of changes in all
Graph API versions.
We are using ASP.NET MVC 5, and we are using or authentication like this:
var facebookAuthenticationOptions = new FacebookAuthenticationOptions()
{
AppId = "****",
AppSecret = "****",
AuthenticationType = "Facebook",
SignInAsAuthenticationType = "ExternalCookie",
Provider = new FacebookAuthenticationProvider
{
OnAuthenticated = async ctx => ctx.Identity.AddClaim(new Claim(ClaimTypes.Email, ctx.User["email"].ToString()))
}
};
facebookAuthenticationOptions.Scope.Add("email");
But today, our login info object, is null in ExternalLoginCallback:
[HttpGet]
[AllowAnonymous]
[RequireHttps]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl = null)
{
try
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
... more code here...
In Facebook Dev. Portal our API Version is 2.3
We have tested many options, with no results:
Access email address in the OAuth ExternalLoginCallback from Facebook v2.4 API in ASP.NET MVC 5
Why new fb api 2.4 returns null email on MVC 5 with Identity and oauth 2?
Thank you much for the help.
I had the same problem and here is how I managed to fix it and get the email from Facebook.
Update following NuGet Pacakges
Microsoft.Owin to version 3.1.0-rc1
Microsoft.Owin.Security to version 3.1.0-rc1
Microsoft.Owin.Security.Cookies to version 3.1.0-rc1
Microsoft.Owin.Security.OAuth to version 3.1.0-rc1
Microsoft.Owin.Security.Facebook to version 3.1.0-rc1
Then add the following code to the Identity Startup class
var facebookOptions = new FacebookAuthenticationOptions()
{
AppId = "your app id",
AppSecret = "your app secret",
BackchannelHttpHandler = new FacebookBackChannelHandler(),
UserInformationEndpoint = "https://graph.facebook.com/v2.8/me?fields=id,name,email,first_name,last_name",
Scope = { "email" }
};
app.UseFacebookAuthentication(facebookOptions);
This is the definition class for FacebookBackChannelHandler():
using System;
using System.Net.Http;
public class FacebookBackChannelHandler : HttpClientHandler
{
protected override async System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
System.Threading.CancellationToken cancellationToken)
{
// Replace the RequestUri so it's not malformed
if (!request.RequestUri.AbsolutePath.Contains("/oauth"))
{
request.RequestUri = new Uri(request.RequestUri.AbsoluteUri.Replace("?access_token", "&access_token"));
}
return await base.SendAsync(request, cancellationToken);
}
}
Just update all the reference related to OWIN
The latest OWIN version is 3.1.0rc1.
This fix the login button, NOT the email, I cannot figure out these issue.
If you cant update OWIn packages because of language packages (as my case) you can
Modify the Identity Startup class code:
var facebookOptions = new FacebookAuthenticationOptions()
{
AppId = "your app id",
AppSecret = "your app secret",
BackchannelHttpHandler = new FacebookBackChannelHandler(),
UserInformationEndpoint = "https://graph.facebook.com/v2.8/me?fields=id,name,email,first_name,last_name",
Scope = { "email" }
};
app.UseFacebookAuthentication(facebookOptions);
This is the definition class for FacebookBackChannelHandler():
public class FacebookBackChannelHandler : HttpClientHandler
{
protected override async System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
if (!request.RequestUri.AbsolutePath.Contains("/oauth"))
{
request.RequestUri = new Uri(request.RequestUri.AbsoluteUri.Replace("?access_token", "&access_token"));
}
var result = await base.SendAsync(request, cancellationToken);
if (!request.RequestUri.AbsolutePath.Contains("/oauth"))
{
return result;
}
var content = await result.Content.ReadAsStringAsync();
var facebookOauthResponse = JsonConvert.DeserializeObject<FacebookOauthResponse>(content);
var outgoingQueryString = HttpUtility.ParseQueryString(string.Empty);
outgoingQueryString.Add(nameof(facebookOauthResponse.access_token), facebookOauthResponse.access_token);
outgoingQueryString.Add(nameof(facebookOauthResponse.expires_in), facebookOauthResponse.expires_in + string.Empty);
outgoingQueryString.Add(nameof(facebookOauthResponse.token_type), facebookOauthResponse.token_type);
var postdata = outgoingQueryString.ToString();
var modifiedResult = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(postdata)
};
return modifiedResult;
}
}
private class FacebookOauthResponse
{
public string access_token { get; set; }
public long expires_in { get; set; }
public string token_type { get; set; }
}

ASP.net MVC: Accessing Google GoogleWebAuthorizationBroker returns access denied error

I'm trying to upload an video to my YouTube account with the following code in my ActionResult in my asp.net MVC project:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Upload([Bind(Include = " Title, Description")] HttpPostedFileBase uploadFile, Videos videos)
{
var credential = AuthYouTube();
YouTubeService youtubeService = new YouTubeService(new
YouTubeService.Initializer()
{
ApplicationName = "app-name",
HttpClientInitializer = credential
});
// Do Stuff with the video here.
}}
The AuthYouTube() looks like this (the same controller):
public UserCredential AuthYouTube()
{
string filePath = Server.MapPath("~/Content/YT/client_secret.json");
UserCredential credential;
try{
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
// This OAuth 2.0 access scope allows for full read/write access to the
// authenticated user's account.
new[] { YouTubeService.Scope.Youtube },
"username#domain.com",
CancellationToken.None,
new FileDataStore(Server.MapPath("~/Content/YT"),true)
).Result;
};
return credential;
}
catch(EvaluateException ex)
{
Console.WriteLine(ex.InnerException);
return null;
}
}
I have stored my client_secret.json that I downloaded from Google Developer Console inside the [project]/Content/YT. (Also tried inside the /App_Data folder.
When uploading the debugger is showing the folowwing message:
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.ComponentModel.Win32Exception: Access is denied
Place where the error occures:
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
StackStrace:
[Win32Exception (0x80004005): Access is denied]
Microsoft.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +115
Microsoft.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccess(Task task) +78
Google.Apis.Auth.OAuth2.<AuthorizeAsync>d__1.MoveNext() in C:\Users\mdril\Documents\GitHub\google-api-dotnet-client\Src\GoogleApis.Auth.DotNet4\OAuth2\GoogleWebAuthorizationBroker.cs:59
[AggregateException: One or more errors occurred.]
System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification) +4472256
Project.Controllers.VideosController.AuthYouTube() in d:\dev\Development\project\project\Controllers\VideosController.cs:133
project.Controllers.VideosController.Upload(HttpPostedFileBase uploadFile, Videos videos) in d:\dev\project\project\Controllers\VideosController.cs:71
What is the reason of this?
- Google API?
- folder / IIS rights?
Update 01-02-2016
Could it be some access error on the API side?
If not, could somebody please provide me the steps to grand the right IIS rights, still get the error after giving folder permissions.
Running the following code DOES create the folder as intended inside my App_Data, but also returns the same 'Access denied' error. The folder is empty.
var path = HttpContext.Current.Server.MapPath("~/App_Data/Drive.Api.Auth.Store");
// here is where we Request the user to give us access, or use the Refresh Token that was previously stored in %AppData%
UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets { ClientId = clientId, ClientSecret = clientSecret }
, scopes
, userName
, CancellationToken.None
, new FileDataStore(path,true)).Result;
Could somebody please explain how to get this working?
After ready the documentation again I found a way to get access to the API and upload my videos to YouTube. I hope I can clarify the way i did this.
How i did this:
https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#web-applications-aspnet-mvc
Create an callback controller:
using Google.Apis.Sample.MVC4;
namespace Google.Apis.Sample.MVC4.Controllers
{
public class AuthCallbackController : Google.Apis.Auth.OAuth2.Mvc.Controllers.AuthCallbackController
{
protected override Google.Apis.Auth.OAuth2.Mvc.FlowMetadata FlowData
{
get { return new AppFlowMetadata(); }
}
}
}
Create class and fill-in the credentials:
using System;
using System.Web.Mvc;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Auth.OAuth2.Mvc;
using Google.Apis.YouTube.v3;
using Google.Apis.Util.Store;
namespace Google.Apis.Sample.MVC4
{
public class AppFlowMetadata : FlowMetadata
{
private static readonly IAuthorizationCodeFlow flow =
new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "PUT_CLIENT_ID_HERE",
ClientSecret = "PUT_CLIENT_SECRET_HERE"
},
Scopes = new[] { YouTubeService.Scope.YoutubeUpload },
DataStore = new FileDataStore(HttpContext.Current.Server.MapPath("~/App_Data/clientsecret.json")),
});
public override string AuthCallback
{
get { return #"/AuthCallback/Upload"; }
}
public override string GetUserId(Controller controller)
{
// In this sample we use the session to store the user identifiers.
// That's not the best practice, because you should have a logic to identify
// a user. You might want to use "OpenID Connect".
// You can read more about the protocol in the following link:
// https://developers.google.com/accounts/docs/OAuth2Login.
var user = controller.Session["user"];
if (user == null)
{
user = Guid.NewGuid();
controller.Session["user"] = user;
}
return user.ToString();
}
public override IAuthorizationCodeFlow Flow
{
get { return flow; }
}
}
}
In my ActionResult I set the YoutubeService. the creating of my video take place inside my Upload POST
Your own controller (mine is for the /upload action):
public async Task<ActionResult> Upload(CancellationToken cancellationToken)
{
var result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).AuthorizeAsync(cancellationToken);
if (result.Credential != null)
{
var youtubeService = new YouTubeService(new BaseClientService.Initializer
{
HttpClientInitializer = result.Credential,
ApplicationName = "name",
});
return View();
}
else
{
return new RedirectResult(result.RedirectUri);
}
}
For uploading logic see: https://developers.google.com/youtube/v3/code_samples/dotnet#upload_a_video
Set redirect URL in Google Developers console
In the Google Developers Console set the Authorized redirect URIs value to something like (my controller is called videos): http://www.domainname.com/Videos/Upload
**Using a single oAuth account **
Insted of saving the client id (GUID, see GetUserId inside AppFlowMetadata file) inside my session I now use one single id so I could use the same token/responsive for all the users.

MVC 5 seeding users only works for email

I am working on a porject built on MVC5 and EF Code First.
I have multiple contexts, but the one I'm concered about here is the ApplicationDbContext which has the following configuration code:
namespace DX.DAL.Migrations.ApplicationDbMigrations
{
public class Configuration : DbMigrationsConfiguration<ApplicationDbContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
MigrationsDirectory = #"Migrations\ApplicationDbMigrations";
ContextKey = "DX.DAL.Context.ApplicationDbContext";
}
protected override void Seed(ApplicationDbContext context)
{
var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(context));
var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(context));
if (!roleManager.RoleExists("Admin"))
{
roleManager.Create(new IdentityRole("Admin"));
}
var user = new ApplicationUser { UserName = "John", Email = "j.doe#world.com" };
if (userManager.FindByName("John") != null) return;
var result = userManager.Create(user, "Password123#");
if (result.Succeeded)
{
userManager.AddToRole(user.Id, "Admin");
}
}
}
}
When I try and login with the email and password seeded above, I get the error:
Invalid login attempt
I wrote the following SQL Query:
SELECT * FROM AspNetUsers
And I see the following:
So the seed has been created. But why can't I login?
Also, I know that if I change the Username to be the same as the email, then it works and I can login. Must the username and email be the same for ASP.NET Membership in MVC 5 to work?
After trying so many different things, I went with LukeP's solution here.
I left Identity as it is and just added a new property called DisplayUsername and allowed the user to set that up on registration.

Resources