Hi, im writing a Blazor Server app that uses Google as an Identity Provider and even though the QuickStart for .Net and some Blog Posts from Auth0 worked for me, when I tried to also get the Refresh Token from Google, it is not sent back to the backend in my requests.
In my tenant, I have a Regular Web Application auth client, the Auth0 Management API
and at last, a Machine-to-machine application that I named Auth0 Management API Intermediator
(which the Auth0 Management API is the only thing authorized), both applications have turn on the refresh token option…
Now in my Blazor Server, in my Project.cs, I have this configuration related to Auth0:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddAuth0WebAppAuthentication(options =>
{
options.Domain = builder.Configuration["Auth0:Domain"];
options.ClientId = builder.Configuration["Auth0:ClientId"];
options.ClientSecret = builder.Configuration["Auth0:ClientSecret"];
options.Scope = " openid profile email offline_access ";
options.ResponseType = "code";
})
// .WithAccessToken(x =>
// {
// x.UseRefreshTokens = true;
// // /oauth/token
// x.Audience = "https://my.domain.auth.com/api/v2/"
// })
;
...
var app = builder.Build();
app.MapGet("/Account/Login", async (HttpContext httpContext, string redirectUri = "/") =>
{
var authenticationProperties = new LoginAuthenticationPropertiesBuilder()
.WithRedirectUri(redirectUri)
.Build();
await httpContext.ChallengeAsync(Auth0Constants.AuthenticationScheme, authenticationProperties);
});
app.MapGet("/Account/Logout", async (HttpContext httpContext, string redirectUri = "/") =>
{
var authenticationProperties = new LogoutAuthenticationPropertiesBuilder()
.WithRedirectUri(redirectUri)
.Build();
await httpContext.SignOutAsync(Auth0Constants.AuthenticationScheme, authenticationProperties);
await httpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
});
app.UseAuthentication();
app.UseAuthorization();
I Also have made a component called AccessControl.razor, as is showed in this post but with some additions:
@inject AuthenticationStateProvider AuthenticationStateProvider
@inject EmailService EmailService
<AuthorizeView>
<Authorized>
<a href="Account/Logout">Log out as @GetUserNameAndEmail()</a>
</Authorized>
<NotAuthorized>
<a href="Account/Login?redirectUri=/">Log in</a>
</NotAuthorized>
</AuthorizeView>
@code {
private async Task Login()
{
await ((Auth0AuthenticationStateProvider)AuthenticationStateProvider)
.LogInAsync();
}
private void Logout()
{
((Auth0AuthenticationStateProvider)AuthenticationStateProvider)
.LogOut();
}
private string GetUserNameAndEmail()
{
try
{
var user = AuthenticationStateProvider
.GetAuthenticationStateAsync()
.Result
.User;
var name = user.FindFirst("name")?.Value;
var email = user.FindFirst(ClaimTypes.Email)?.Value;
// some validation...
EmailService.RequestClientCredentialsToken(email);
return $"{name} ({email})";
}
catch (Exception e)
{
Console.WriteLine(e);
return "???";
}
}
}
After I get the email and add it to the EmailService, it calls 3 methods in sequence, RequestClientCredentialsToken()
get an access_token to use the Auth0 Management API, RequestUserDataByEmail()
load the user data from the Auth0 database and then GetGmailUserProfile()
load the user’s gmail profile (Google API reference):
public class EmailService(ConfigurationManager config)
{
private void RequestClientCredentialsToken(string userEmailAddress)
{
//Uses the credentials of the Machine-to-Machine Application to request an access_token
var mtmId = config.GetValue<string>("Auth0:MachineToMachineId");
var mtmSecret = config.GetValue<string>("Auth0:MachineToMachineSecret");
var audience = "https://my.domain.auth0.com/api/v2/";
var client = new RestClient("https://my.domain.auth0.com/oauth/token/");
var request = new RestRequest("", Method.Post);
// im doing in this way bc seems more readable
StringBuilder sb = new StringBuilder();
sb.Append('{');
sb.Append($" \"client_id\": \"{mtmId}\", ");
sb.Append($" \"client_secret\": \"{mtmSecret}\", ");
sb.Append($" \"audience\": \"{audience}\", ");
sb.Append($" \"grant_type\": \"client_credentials\" ");
sb.Append('}');
var body = sb.ToString();
request.AddBody(body);
request.AddHeader("content-type", "application/json");
var response = client
.ExecuteAsync<TokenRequest>(request)
.Result;
RequestUserDataByEmail(userEmailAddress, response.Data?.access_token);
}
private void RequestUserDataByEmail(string userEmailAddress, string? auth0ApiToken)
{
//Calls Get Users by Email (Auth0 Management API) and gets the User Data
var client = new RestClient("https://my.domain.auth0.com/api/v2/users-by-email?email=" + SanitizeEmail(userEmailAddress));
var request = new RestRequest();
request.AddHeader("content-type", "application/json");
request.AddHeader("authorization", $"Bearer {auth0ApiToken}");
try
{
var response = client
.ExecuteAsync<List<Auth0UserModel>>(request)
.Result
.Data;
GetGmailUserProfile(
response[0].identities[0].access_token,
response[0].identities[0].user_id
);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
private void GetGmailUserProfile(string idpToken, string userId)
{
var client = new RestClient($"https://gmail.googleapis.com/gmail/v1/users/{userId}/profile?access_token={idpToken}");
var request = new RestRequest();
var googleApiKey = config.GetValue<string>("Google:ApiKey");
request.AddHeader("content-type", "application/json");
request.AddHeader("authorization", googleApiKey);
try
{
var response = client
.ExecuteAsync<Profile>(request)
.Result;
var responseObj = response.Content;
Console.WriteLine("Response Gmail Profile: " + responseObj);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
private static string SanitizeEmail(string email) => email.Replace("@", "%40");
}
And this approach works, but I need to redo the login on my application every 1 hour, so having a refresh token would facilitate much of my work… but I’m a little confused by the documentation and other people’s post here about this same subject
I have added the ReturnType = "code"
on the Program.cs , added the “offline_access” in the scopes, tried using .WithAccessToken()
, but I didn’t receive it. I’m not sure where is the issue, I assume it’s some configuration missing…
Edit: grammar