我是 React/next.js 新手,我正在尝试将我的应用程序设置为使用后端 .NET 8 WEB API。
我通过此链接设置了 WEB API。
这个效果很好。当我在 VS 2022 中启动 WEB API 时,它要求我登录 Clerk.com,然后显示我的 WEB API 调用。
现在我尝试通过我的 next.js 应用程序调用相同的 WEB API。
最后,我希望用户登录我的 next.js 应用程序,然后使用该登录验证传递到我的 .NET 8 WEB API 以登录该应用程序,然后能够与 WEB API 正确对话。
现在,我必须确保我已退出 WEB API,然后通过 Clerk.com 登录 next.js。然后单击调用 WEB API 的按钮,它要求我再次登录 Clerk.com。 完成此操作后,它会收到 302 已找到。似乎没有传递进行身份验证所需的项目?
这是我的 next.js 代码:
"use client"
import { useEffect, useState } from 'react';
import { WeatherForecast } from '../interfaces/WeatherForecastModel'; // Adjust the import path as necessary
import { enableRipple } from '@syncfusion/ej2-base';
import { ButtonComponent } from '@syncfusion/ej2-react-buttons'
import { useAuth, useClerk } from '@clerk/clerk-react'
enableRipple(true);
const Index = () => {
const { getToken } = useAuth()
const [token, setToken] = useState(''); // Manage token with useState
const [clerkOther, setclerkOther] = useState(''); // Manage token with useState
const [data, setData] = useState<WeatherForecast[]>([]);
const { user } = useClerk();
const fetchData = async (url: string) => {
const token = await getToken();
const clerkOther = user?.publicMetadata.clerk_token as string;
setToken(token ?? ''); // Update the token state
setclerkOther(clerkOther); // Update the token state
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
mode: 'no-cors',
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
};
const clickEventHandlerWeather = async () => {
try {
const jsonData = await fetchData('https://localhost:7160/WeatherForecast');
setData(jsonData);
} catch (error) {
console.error('Failed to fetch weather data:', error);
}
};
const clickEventHandlerLogin = async () => {
try {
await fetchData('https://localhost:7160/Login');
// For login, assuming you might not want to set any specific data
} catch (error) {
console.error('Failed to login:', error);
}
};
return (
<div className="row">
<div className='col-md-6'>
<ButtonComponent cssClass='e-danger' onClick={clickEventHandlerLogin}>Login</ButtonComponent>
</div>
<br></br><br></br>
<div className='col-md-6'>
<ButtonComponent cssClass='e-danger' onClick={clickEventHandlerWeather}>Weather</ButtonComponent>
</div>
{/* Display token. Ensure you handle displaying sensitive information securely */}
<div style={{ width: '200px' }}>{token}</div>
<br></br>
<div style={{ width: '200px' }}>clerkOther: {token}</div>
</div>
);
};
export default Index;
注意:另一个问题是 CORS。我知道我不需要这样做,但这也导致了其他错误。所以我打算在即将使用 WEB API 时进行研究。
和Clerk.net。 Program.cs 的一部分
builder.Services.AddClerkApiClient(config =>
{
config.SecretKey = builder.Configuration["ClerkSecretKey"];
});
builder.Services.AddJwtBearerAuthentication(builder.Configuration, builder.Services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>());
应用 JWT 检查(这可以在某些时候重构)
namespace WebAPINext.Configuration
{
public static class JwtBearerAuthenticationConfiguration
{
public static void AddJwtBearerAuthentication(
this IServiceCollection services, IConfiguration configuration, IHttpClientFactory httpClientFactory)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
// Assume publicKey is your Clerk instance's public RSA key in PEM format
var pemFormat = configuration["ClerkPublicKeyPemFormat"];
if (pemFormat == null)
{
throw new Exception(nameof(pemFormat));
}
RSA rsa;
try
{
rsa = RSA.Create();
rsa.ImportFromPem(pemFormat.ToCharArray());
}
catch (System.ArgumentException)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent("Invalid RSA key format."),
ReasonPhrase = "Invalid Request"
});
}
options.Authority = configuration["ClerkAuthority"];
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
IssuerSigningKey = new RsaSecurityKey(rsa),
ValidateIssuer = false,
ClockSkew = TimeSpan.Zero
};
options.Events = new JwtBearerEvents
{
OnTokenValidated = async context =>
{
var azp = context.Principal?.FindFirstValue("azp");
if (string.IsNullOrEmpty(azp) || !azp.Equals(configuration["ClerkAuthorizedParty"]))
{
context.Fail("AZP Claim is invalid or missing");
return;
}
await SetClaims(context, configuration, httpClientFactory).ConfigureAwait(false);
},
};
});
}
// Claims can be saved in JWT via Clerk.com
private static async Task SetClaims(TokenValidatedContext context, IConfiguration configuration, IHttpClientFactory httpClientFactory)
{
try
{
// Assuming 'configuration' and 'context' are available in the scope
var clerkApiKey = configuration["ClerkSecretKey"];
if (string.IsNullOrEmpty(clerkApiKey))
{
throw new InvalidOperationException("Clerk API key is not configured properly.");
}
var userIdClaim = context.Principal.Claims.FirstOrDefault(c => c.Type == "user_id" || c.Type == ClaimTypes.NameIdentifier);
if (userIdClaim == null || string.IsNullOrEmpty(userIdClaim.Value))
{
throw new InvalidOperationException("User ID claim is not available.");
}
string userId = userIdClaim.Value;
string clerkApiUrl = $"https://api.clerk.com/v1/users/{userId}";
using var client = httpClientFactory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", clerkApiKey);
var response = await client.GetAsync(clerkApiUrl);
if (!response.IsSuccessStatusCode)
{
Console.WriteLine("Failed to retrieve user details from Clerk.");
return;
}
var responseBody = await response.Content.ReadAsStringAsync();
using var jsonDoc = JsonDocument.Parse(responseBody);
var user = jsonDoc.RootElement;
var id = user.GetProperty("id").GetString();
var emailAddress = user.GetProperty("email_addresses")[0].GetProperty("email_address").GetString();
var firstName = user.GetProperty("first_name").GetString();
var lastName = user.GetProperty("last_name").GetString();
var userName = user.GetProperty("username").GetString();
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, id),
new Claim(ClaimTypes.Name, userName),
new Claim("firstName", firstName),
new Claim("lastName", lastName),
new Claim(ClaimTypes.Email, emailAddress),
};
if (context.Principal.Identity is ClaimsIdentity claimsIdentity)
{
claimsIdentity.AddClaims(claims);
}
}
catch (Exception ex)
{
Console.WriteLine($"Error in VerifyTokenWithClerkAsync: {ex.Message}");
}
}
}
}