Код интерфейса (AuthContext) .js):
- функция makeAuthenticatedRequest обрабатывает аутентифицированные вызовы API и
пытается обновить токен в случае сбоя.
< li>getFreshToken управляет обновлением токена с ограничением в 5 минут
Код: Выделить всё
// AuthContext.js
const getFreshToken = async (force = false) => {
if (!user) return null;
const now = Date.now();
if (force || now - lastTokenRefresh > 5 * 60 * 1000) {
try {
const token = await user.getIdToken(true);
setLastTokenRefresh(now);
return token;
} catch (error) {
console.error('Error refreshing token:', error);
return null;
}
}
return user.getIdToken();
};
const makeAuthenticatedRequest = async (url, options = {}, retryCount = 0) => {
if (!user) {
throw new Error('User not authenticated');
}
try {
const token = await getFreshToken(retryCount > 0);
if (!token) throw new Error('Failed to get authentication token');
const response = await fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${token}`,
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
if (!response.ok) {
const errorData = await response.json();
if (errorData.message?.includes('expired') && retryCount < 2) {
await new Promise(resolve => setTimeout(resolve, 1000));
return makeAuthenticatedRequest(url, options, retryCount + 1);
}
throw new Error(JSON.stringify(errorData));
}
return await response.json();
} catch (error) {
console.error('API request failed:', error);
throw error;
}
};
useEffect(() => {
const checkUserRole = async () => {
if (user) {
try {
const response = await makeAuthenticatedRequest('http://localhost:5067/api/auth/protected');
setIsAdmin(response.user.isAdmin);
} catch (error) {
console.error('Error checking user role:', error);
setIsAdmin(false);
}
} else {
setIsAdmin(false);
}
};
checkUserRole();
}, [user]);
useEffect(() => {
if (!user) return;
const refreshInterval = setInterval(async () => {
await getFreshToken(true);
}, 10 * 60 * 1000);
return () => clearInterval(refreshInterval);
}, [user]);
Код: Выделить всё
// AuthController.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using FirebaseAdmin.Auth;
using UniLostAndFound.API.Services;
using UniLostAndFound.API.Models;
namespace UniLostAndFound.API.Controllers;
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly ILogger _logger;
private readonly FirestoreService _firestoreService;
public AuthController(ILogger logger, FirestoreService firestoreService)
{
_logger = logger;
_firestoreService = firestoreService;
}
[HttpGet("test")]
public ActionResult TestPublic()
{
return Ok(new { message = "Public endpoint working!" });
}
[HttpGet("protected")]
public async Task TestProtected()
{
try
{
string authHeader = Request.Headers["Authorization"].ToString();
_logger.LogInformation($"Auth Header received: {authHeader.Length} characters");
if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer "))
{
return Unauthorized(new { message = "No Bearer token found" });
}
string idToken = authHeader.Substring("Bearer ".Length);
try
{
var firebaseToken = await FirebaseAuth.DefaultInstance.VerifyIdTokenAsync(idToken);
string email = firebaseToken.Claims.GetValueOrDefault("email", "").ToString();
string name = firebaseToken.Claims.GetValueOrDefault("name", "").ToString();
// Check if email is allowed
if (!await _firestoreService.IsAllowedEmail(email))
{
return Unauthorized(new { message = "Email domain not allowed" });
}
// Check if user is admin
bool isAdmin = await _firestoreService.IsAdminEmail(email);
return Ok(new
{
message = "Authentication successful",
user = new
{
uid = firebaseToken.Uid,
email = email,
name = name,
isAdmin = isAdmin
}
});
}
catch (FirebaseAuthException ex)
{
_logger.LogWarning($"Token verification failed: {ex.Message}");
return Unauthorized(new { message = ex.Message });
}
catch (Exception ex)
{
_logger.LogError($"Unexpected error during token verification: {ex.Message}");
return StatusCode(500, new { message = "Internal server error during authentication" });
}
}
catch (Exception ex)
{
_logger.LogError($"Unexpected error: {ex.Message}");
return StatusCode(500, new { message = "Internal server error" });
}
}
}
Код: Выделить всё
// Update the JWT Bearer configuration
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
var projectId = "unilostandfound";
options.Authority = $"https://securetoken.google.com/{projectId}";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = $"https://securetoken.google.com/{projectId}",
ValidateAudience = true,
ValidAudience = projectId,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5)
};
});
- 401 Несанкционировано: при выполнении аутентифицированных запросов из
интерфейса я часто получаю сообщение Ошибка 401. - Срок действия токена: сообщение об ошибке указывает на то, что срок действия токена Firebase ID
истек. Я думал, что функция getFreshToken() в моем
AuthContext.js справится с обновлением, но, похоже,
иногда она дает сбой.
- Реализовать механизм обновления токена, который пытается получить новый ID
токен, если срок действия текущего истек.
Повторная попытка запроса API один раз, если обнаружена ошибка 401.
Подробнее здесь: https://stackoverflow.com/questions/791 ... t-api-call