Closed tuugen closed 1 year ago
You could try setting SaveTokens
to true
and then accessing it from the authentication properties.
Otherwise the callback event is the way to get access to the raw API payload using the User
property of the event context.
You could try setting
SaveTokens
totrue
and then accessing it from the authentication properties.Otherwise the callback event is the way to get access to the raw API payload using the
User
property of the event context.
Thanks for the suggestion @martincostello , heres what I tried:
So I added the SaveTokens
flag...
.AddDiscord(options =>
{
options.ClientId = builder.Configuration["Authentication:Discord:ClientId"];
options.ClientSecret = builder.Configuration["Authentication:Discord:ClientSecret"];
options.SaveTokens = true;
...
options.Events.OnCreatingTicket = async (ctx) =>
{
//example 'TicketCreated' taken from https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/additional-claims?view=aspnetcore-6.0#map-user-data-keys-and-create-claims
List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
tokens.Add(new AuthenticationToken()
{
Name = "TicketCreated",
Value = DateTime.UtcNow.ToString()
});
//remove accessToken code for now.. just try and get placeholder 'TicketCreated' Visible/accessible from outside this function....
ctx.Properties.StoreTokens(tokens);
};
So after logging in , I put this code in my index.cshtml.cs razor page:
//inside index.cshtml.cs
public async void OnGet()
{
Console.WriteLine("got properties: ");
//https://stackoverflow.com/a/73301094/5198805
AuthenticationProperties props = HttpContext.Features.Get<IAuthenticateResultFeature>()?.AuthenticateResult?.Properties;
if(props != null){
foreach(var item in props.Items){
Console.WriteLine(item.Key + " - " + item.Value);
}
}
}
which gives
got properties:
.issued - Mon, 29 Aug 2022 06:53:34 GMT
.expires - Mon, 29 Aug 2022 06:58:34 GMT
I was expecting it would have some Discord related stuff... atleast the TicketCreated
set above?
Not sure if this is related but in my program.cs
I am setting the user with
builder.Services.AddDefaultIdentity<MyExtendedUserClass>
whose descriptoin reads:
Adds a set of common identity services to the application, including a default UI, token providers, and configures authentication to use identity cookies.
To use the tokens elsewhere I think you want to use the HttpContext.GetTokenAsync(string)
method similar to this code of my own that uses the GitHub API and authenticates as the logged in user.
Ok, well I cloned the sample project again, added in the few lines for SaveToken = true
and to print out access_token
. It works there... but for some reason in my already established project ... the access_token is empty:
IN sample MVC.Client HomeController
public async Task<ActionResult> IndexAsync()
{
Console.WriteLine("is Authenticated? " + User?.Identity?.IsAuthenticated);
Console.WriteLine("Authenticated Identity Type: " + User?.Identity?.AuthenticationType);
Console.WriteLine("fetching access token..");
var token = await HttpContext.GetTokenAsync("access_token");
Console.WriteLine("got token: " + token);
return View();
}
results in (token replaced by XXXXXXXXXXXXX)
is Authenticated? True
Authenticated Identity Type: Discord
fetching access token..
got token: XXXXXXXXXXXXXXXXXXXXXXX
However, in my own project...
//in controller
public async void OnGet()
{
Console.WriteLine("is Authenticated? " + User.Identity.IsAuthenticated);
Console.WriteLine("Authenticated Identity Type: " + User.Identity.AuthenticationType);
Console.WriteLine("fetching access token..");
var token = await HttpContext.GetTokenAsync("access_token");
Console.WriteLine("got token: " + token);
Console.WriteLine("fetching explicit discord access token..");
var discordToken = await HttpContext.GetTokenAsync("Discord", "access_token");
Console.WriteLine("got discord token: " + discordToken);
}
output:
is Authenticated? True
Authenticated Identity Type: Identity.Application
got token:
fetching explicit discord access token..
got discord token:
info: AspNet.Security.OAuth.Discord.DiscordAuthenticationHandler[7]
Discord was not authenticated. Failure message: Not authenticated
I Think I found the issue... The Authenticated Identity Type int he sample is "Discord", but in my own project it is "Identity.Application". I am trying to graft on Discord OAuth2.0 As an optional login path, onto an existing Microsoft.Identity app I built using their tutorial docs.
Is there some way to reconcile these differences?
I'm afraid I don't know the answer to that one, and I think your problem is out of the scope of our repo.
I think your problem is going to be at a generic level with any OAuth provider (we build on top of Microsoft's OAuth implementation, OAuthHandler
) and MS Identity, rather than any problem with the Discord provider specifically.
That's because you exchange the discord identity for a local identity. The discord tokens can be stored in the local user database. @haok, where are the docs for that? All I can find is https://docs.microsoft.com/en-us/aspnet/core/security/authentication/customize-identity-model?view=aspnetcore-6.0#entity-types
This one is probably the one you are looking for with access tokens: https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/additional-claims?view=aspnetcore-6.0#save-the-access-token
This one is probably the one you are looking for with access tokens: https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/additional-claims?view=aspnetcore-6.0#save-the-access-token
Hmmm. I've implemented that example by scaffolding out that portion via
dotnet aspnet-codegenerator identity --dbContext ApplicationDbContext --files Account.ExternalLogin
however it seems their provided code in that link (that you copy paste into the scaffold file...) only runs on initial user/account creation via ExternalLogin, and sets a Claim in the database. (I may be mistaken here... but i tihnk normal login goes OnPost -> OnGetCallbackAsync , while, OnPostConfirmationAsync, the place where they say to put all the new code, is only run on initial creation, hence the code about email account confirmation at the end of it)
The issue I have here is that I can't seem to get the accessToken of discord/google/etc... from my razor page when calling as such:
Console.WriteLine("is Authenticated? " + User.Identity.IsAuthenticated);
Console.WriteLine("Authenticated Identity Name: " + User.Identity.Name);
Console.WriteLine("Authenticated Identity Type: " + User.Identity.AuthenticationType);
var discordToken = await HttpContext.GetTokenAsync("Discord", "access_token");
Console.WriteLine("got discord token: " + discordToken);
var googleToken = await HttpContext.GetTokenAsync("Google", "access_token");
Console.WriteLine("got google token: " + googleToken);
generates
Authenticated Identity Name: <my-email>@gmail.com
Authenticated Identity Type: Identity.Application
got discord token:
got google token:
info: AspNet.Security.OAuth.Discord.DiscordAuthenticationHandler[7]
Discord was not authenticated. Failure message: Not authenticated
however, I logged in with discord and had it print out all the accessToken stuff in the Event.ctx callback. Its in the aspnet program... just not able to expose it in controller/pages... hmmm.
Perhaps if there a boilerplate reference you guys have that uses the aspnet-contrib/AspNet.Security.OAuth.Providers alongside the built in microsoft.indentity... I could build off that?
e.g. if MVC.Client had a sibling project using the dotnet new webapp --auth Individual -o WebApp1
template as a base, given in their identity
section tutorial https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-6.0&tabs=visual-studio
In the meantime, I'll try a workaround using the basic OAuth primitives like @martincostello mentioned.
That's because you exchange the discord identity for a local identity. The discord tokens can be stored in the local user database. @HaoK, where are the docs for that?
I suppose as a workaround I can also do this , yes. https://security.stackexchange.com/questions/72475/should-we-store-accesstoken-in-our-database-for-oauth2
I just wish it was there in memory to use calling await HttpContext.GetTokenAsync("Discord", "access_token");
seems like a 'usecase' alot of people will have? (Using a normal microsoft.identity auth system and then adding on the OAuth.Providers repo for quick access_token access/integration)
Ok HUGE,
I found that by adding
await HttpContext.SignInAsync(info.Principal, info.AuthenticationProperties);
to OnGetCallbackAsync
in Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs
as such, allows the await HttpContext.GetTokenAsync("Discord", "access_token");
to work inside any arbitary razor page file.
//ExternalLogin.cshtml.cs generated by dotnet aspnet-codegenerator identity --dbContext ApplicationDbContext --files Account.ExternalLogin
public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (remoteError != null)
{
ErrorMessage = $"Error from external provider: {remoteError}";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
Console.WriteLine("====Signing In Async");
// 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, bypassTwoFactor: true);
if (result.Succeeded)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("AuthenticaionProperties set after logging in: ");
Console.ResetColor();
foreach(var item in info.AuthenticationProperties.Items){
Console.WriteLine(item.Key + " - " + item.Value);
}
//The Missing Piece!
await HttpContext.SignInAsync(info.Principal, info.AuthenticationProperties);
_logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider);
return LocalRedirect(returnUrl);
}
if (result.IsLockedOut)
{
return RedirectToPage("./Lockout");
}
else
{
// If the user does not have an account, then ask the user to create an account.
ReturnUrl = returnUrl;
ProviderDisplayName = info.ProviderDisplayName;
if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Email))
{
Input = new InputModel
{
Email = info.Principal.FindFirstValue(ClaimTypes.Email)
};
}
return Page();
}
}
For now this will work. closing!
Provider name : Discord
Hello! Is there a way to acquire the access token attached to the User? I have successfully logged into my Oauth app with the 'guilds' permission. I would now like to add a button on a MVC razor page to list the guilds I am part of.
my discord config is as such
however, when I try and access the claims on a index.cshtml page (using the MVC.Client boilerplate in this project...)
I only get the following
how do i get the discord avatar url &guilds
info defined above in the options?How can i get the Oauth Access token other than getting it through that Events callback?
Can I somehow attach access token to my
User
object in MVC controllers for easy access?Apologies If there is documentation somewhere describing all of this, I just can't find it.