I want Telegram to do the user account management for my ASP.NET 5.0 Webapp
https://core.telegram.org/widgets/login
I got it working to the point that the Telegram servers redirect to my provided callback address, where I call _userManager.SignInAsync, but when later checking whether the user is logged in, SignInManager always returns false.
The callback is implemented like this:
public class AccountController : Controller
{
private readonly IConfiguration _config;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
public AccountController(IConfiguration config,
SignInManager<ApplicationUser> signInManager,
UserManager<ApplicationUser> userManager)
{
_config = config;
_signInManager = signInManager;
_userManager = userManager;
}
[HttpGet]
public async Task<IActionResult> Logout()
{
await _signInManager.SignOutAsync();
return RedirectToAction("index", "home");
}
[HttpGet]
public async Task<IActionResult> Login()
{
await Task.CompletedTask;
var botname = _config["BOT_NAME"];
var fqdn = _config["FQDN"];
ViewBag.WidgetEmbedCode = WidgetEmbedCodeGenerator.GenerateRedirectEmbedCode(
_config["BOT_NAME"],
$"https://{fqdn}{Url.Action("tgcallback", "account")}",
ButtonStyle.Large,
true,
true);
return View();
}
[HttpGet]
public async Task<IActionResult> Tgcallback(
string id,
string first_name,
string username,
string photo_url,
string auth_date,
string hash)
{
// attempt to authenticate the login
var token = _config["BOT_TOKEN"];
var loginWidget = new LoginWidget(token);
var auth = loginWidget.CheckAuthorization(new SortedDictionary<string, string>()
{
{"id",id},
{"first_name", first_name},
{"username", username},
{"photo_url", photo_url},
{"auth_date", auth_date},
{"hash", hash}
});
// if the authorization was successful, create the user (if not exist) and sign in
if (auth == Authorization.Valid)
{
var user = await _userManager.FindByNameAsync($"tg{id}");
if (null == user)
{
user = new ApplicationUser()
{
UserName = $"tg{id}",
TelegramNativeId = long.Parse(id),
TelegramUserName = username,
FirstName = first_name,
PhotoUrl = photo_url
};
var result = await _userManager.CreateAsync(user);
if (!result.Succeeded)
{
ViewBag.ErrorTitle = "Internal error";
ViewBag.ErrorMessage = $"Failed to create user tg{id}";
return View("Error");
}
user = await _userManager.FindByNameAsync($"tg{id}");
if (null == user)
{
ViewBag.ErrorTitle = "Internal error";
ViewBag.ErrorMessage = $"Failed to create user tg{id}";
return View("Error");
}
}
await _signInManager.SignInAsync(user, true);
}
// return him back to home/index where he will be redirected to login,
// if the login was unsuccessful
return RedirectToAction("index", "home");
}
}
After the callback is invoked, a user (without password hash) is created in the DB and I have a cookie from .AspNetCore.Identity.Application
. I am then redirected to /home/index which is implemented like this:
public class HomeController : Controller
{
private SignInManager<ApplicationUser> _signInManager;
public HomeController(SignInManager<ApplicationUser> signInManager)
{
_signInManager = signInManager;
}
public async Task<IActionResult> Index()
{
if (_signInManager.IsSignedIn(User))
{
var user = await _signInManager.UserManager.GetUserAsync(User);
var model = new HomeIndexViewmodel()
{
TgNativeId = user.TelegramNativeId.ToString(),
FirstName = user.TelegramUserName,
UserName = user.TelegramUserName,
PhotoUrl = user.PhotoUrl
};
return View(model);
}
else
{
return RedirectToAction("login", "account");
}
}
}
which in turn redirects me back to the login page; hence thinking that I am not logged in.
What am I missing?
Edit: Maybe I have not configured Identity correctly. This is my ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
// Configuring Telegram Bot
services.AddHostedService<TelegramBotHostedService>();
services.AddHttpClient("tgwebhook")
.AddTypedClient<ITelegramBotClient>(httpClient => new TelegramBotClient(_config["BOT_TOKEN"], httpClient));
services.AddSingleton<ITelegramBotUpdateHandler, TelegramBotUpdateHandlerImpl>();
// Configuring database connection
services.AddDbContextPool<ApplicationDbContext>(
options => options.UseNpgsql(_config["DB_CONNECTION_STRING"]));
// Configuring for account management
services.AddIdentity<ApplicationUser, IdentityRole>(options => {})
.AddEntityFrameworkStores<ApplicationDbContext>();
// Configuring MVC and NewtonsoftJson on which Telegram.Bot is highly dependent
services.AddMvc()
.AddNewtonsoftJson();
}
The problem was that I did not add authentication to the request processing pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// ----------------------------------------------------------------
// run ef core database migration every time the app starts
// feels hacky and this is properly not the right place to call it
// but according to
// https://entityframeworkcore.com/knowledge-base/48617880/ef-core-migrations-in-docker-container
// this is the way to do it
using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
serviceScope.ServiceProvider.GetRequiredService<ApplicationDbContext>().Database.Migrate();
}
// ----------------------------------------------------------------
// from here onwards the request processing pipeline is created
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseEndpoints(endpoints =>
{
// setting up the route for the bot
var token = _config["BOT_TOKEN"];
endpoints.MapControllerRoute(
"tgwebhook",
$"bot/{token}",
new { controller = "TelegramBotWebhook", Action = "Post" });
// setting up routes for controllers
endpoints.MapControllerRoute(
"default",
"{controller=Home}/{action=Index}/{id?}");
/*
// forward all other requests to here
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
*/
});
}
}