Реализация авторизации пользователя в браузерном расширении
Авторизация в расширении — технически сложнее, чем в обычном веб-приложении: нет встроенных сессий, нельзя использовать httpOnly-куки, токены нужно хранить в chrome.storage, а OAuth2 flow требует специальной обработки.
OAuth2 через chrome.identity API
// manifest.json
{
"permissions": ["identity", "storage"],
"oauth2": {
"client_id": "YOUR_GOOGLE_CLIENT_ID",
"scopes": ["openid", "email", "profile"]
}
}
// Авторизация через Google OAuth2
async function authenticateWithGoogle() {
return new Promise((resolve, reject) => {
chrome.identity.getAuthToken({ interactive: true }, async (token) => {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
return;
}
// Обмениваем Google token на токен нашего приложения
const resp = await fetch('https://api.example.com/v1/auth/google', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ google_token: token }),
});
const { access_token, refresh_token } = await resp.json();
await chrome.storage.local.set({
access_token,
refresh_token,
token_expiry: Date.now() + 3600 * 1000,
});
resolve({ access_token });
});
});
}
Авторизация через собственный сервис (email/password)
// Открываем страницу авторизации в новой вкладке
async function loginWithCredentials() {
const loginUrl = `https://app.example.com/extension-login?` +
`redirect_uri=${encodeURIComponent('https://app.example.com/extension-callback')}`;
// Открываем страницу логина
chrome.tabs.create({ url: loginUrl });
// Слушаем сообщение от страницы после авторизации
return new Promise((resolve) => {
const listener = (message) => {
if (message.type === 'AUTH_SUCCESS') {
chrome.runtime.onMessage.removeListener(listener);
storeTokens(message.tokens);
resolve(message.tokens);
}
};
chrome.runtime.onMessage.addListener(listener);
});
}
// На странице /extension-callback после авторизации:
// window.postMessage('extension-auth', ...) или chrome.runtime.sendMessage
Хранение и обновление токенов
class TokenManager {
async getValidToken() {
const stored = await chrome.storage.local.get(['access_token', 'refresh_token', 'token_expiry']);
if (stored.access_token && stored.token_expiry > Date.now() + 60000) {
return stored.access_token;
}
// Нужно обновить токен
if (stored.refresh_token) {
return this.refreshToken(stored.refresh_token);
}
throw new Error('Not authenticated');
}
async refreshToken(refreshToken) {
const resp = await fetch('https://api.example.com/v1/auth/refresh', {
method: 'POST',
body: JSON.stringify({ refresh_token: refreshToken }),
});
const tokens = await resp.json();
await chrome.storage.local.set({
access_token: tokens.access_token,
refresh_token: tokens.refresh_token,
token_expiry: Date.now() + tokens.expires_in * 1000,
});
return tokens.access_token;
}
async logout() {
await chrome.storage.local.remove(['access_token', 'refresh_token', 'token_expiry']);
chrome.identity.clearAllCachedAuthTokens(() => {});
}
}
Popup UI для авторизации
// popup.tsx
import { useState, useEffect } from 'react';
export function Popup() {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
chrome.storage.local.get(['access_token'], async ({ access_token }) => {
if (access_token) {
const profile = await fetchUserProfile(access_token);
setUser(profile);
}
});
}, []);
if (!user) {
return (
<div className="p-4 w-72">
<h1 className="text-lg font-bold mb-4">Войти</h1>
<button
onClick={() => chrome.runtime.sendMessage({ action: 'login' })}
className="btn-primary w-full"
>
Войти через Google
</button>
</div>
);
}
return (
<div className="p-4 w-72">
<div className="flex items-center gap-3">
<img src={user.avatar} className="w-8 h-8 rounded-full" />
<span>{user.name}</span>
</div>
</div>
);
}
Сроки
Авторизация в расширении с OAuth2 и refresh-токенами: 3–5 рабочих дней.







