jwt refresh token 1.0

This commit is contained in:
2025-09-21 17:49:55 +03:00
parent 639bc3c1a9
commit 5ddab94773
8 changed files with 95 additions and 19 deletions

2
.env
View File

@@ -2,7 +2,7 @@
SECRET_KEY=SUPER_SECRET_KEY SECRET_KEY=SUPER_SECRET_KEY
ALGORITHM=HS256 ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=30 ACCESS_TOKEN_EXPIRE_MINUTES=30
REFRESH_TOKEN_EXPIRE_MINUTES=6000
# CORS-middleware # CORS-middleware
# ALLOW_ORIGINS=*, # "*" — разрешить всем; можно указать список конкретных доменов # ALLOW_ORIGINS=*, # "*" — разрешить всем; можно указать список конкретных доменов
# ALLOW_CREDENTIALS=True, # ALLOW_CREDENTIALS=True,

View File

@@ -3,21 +3,47 @@ from jose import JWTError, jwt
from fastapi import HTTPException, Depends, status from fastapi import HTTPException, Depends, status
from fastapi.security import OAuth2PasswordBearer from fastapi.security import OAuth2PasswordBearer
from server.database import db
from dotenv import load_dotenv #Работа с env для jwt from dotenv import load_dotenv #Работа с env для jwt
import os import os
load_dotenv() load_dotenv()
SECRET_KEY = os.getenv('SECRET_KEY') SECRET_KEY = os.getenv('SECRET_KEY')
ALGORITHM = os.getenv('ALGORITHM') ALGORITHM = os.getenv('ALGORITHM')
ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv('ACCESS_TOKEN_EXPIRE_MINUTES')) ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv('ACCESS_TOKEN_EXPIRE_MINUTES'))
REFRESH_TOKEN_EXPIRE_MINUTES = int(os.getenv('REFRESH_TOKEN_EXPIRE_MINUTES'))
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login") #Создание jwt oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login") #Создание jwt
async def create_access_token(data: dict, expires_delta: timedelta | None = None): class Token():
to_encode = data.copy() @staticmethod
expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15)) async def create_token(data: dict, expires_delta: timedelta | None = None):
to_encode.update({"exp": expire}) to_encode = data.copy()
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15))
return encoded_jwt to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
class AccessToken(Token):
@staticmethod
async def create(data:dict, expires_delta: timedelta | None = None):
return await Token.create_token(data, expires_delta)
class RefreshToken(Token):
@staticmethod
async def create(data:dict, expires_delta: timedelta | None = None):
token_str = await Token.create_token(data, expires_delta)
await db.refresh_token(encoded_jwt = token_str,email=data["sub"])
return token_str
# async def create_access_token(data: dict, expires_delta: timedelta | None = None):
# to_encode = data.copy()
# expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15))
# to_encode.update({"exp": expire})
# encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
# return encoded_jwt
# async def create_refresh_token(data:dict, expires_delta:timedelta | None = None):
# to_encode = data.copy()
# expire = datetime.utcnow() + (expires_delta or timedelta(minutes=6000))
# to_encode.update({"exp": expire})
# encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
# return encoded_jwt
async def current_user(token: str = Depends(oauth2_scheme)): #Проверка jwt async def current_user(token: str = Depends(oauth2_scheme)): #Проверка jwt
try: try:

View File

@@ -87,8 +87,16 @@ async def login_user(row: pydentic.UserLogin):
if not user: if not user:
raise HTTPException(status_code=401, detail="The user isn't found") raise HTTPException(status_code=401, detail="The user isn't found")
token = await JWT.create_access_token( access_token = await JWT.AccessToken.create(
{"sub": user.email}, {"sub": user.email},
timedelta(minutes=JWT.ACCESS_TOKEN_EXPIRE_MINUTES) timedelta(minutes=JWT.ACCESS_TOKEN_EXPIRE_MINUTES)
) )
return {"access_token": token, "token_type": "bearer"} refresh_token = await JWT.RefreshToken.create(
{"sub": user.email},
timedelta(minutes=JWT.REFRESH_TOKEN_EXPIRE_MINUTES)
)
return {
"access_token": access_token,
"refresh_token": refresh_token,
"token_type": "bearer"
}

View File

@@ -33,6 +33,7 @@ class User(Base):
description = Column(String, nullable=False) description = Column(String, nullable=False)
activated = Column(Boolean, default=False) activated = Column(Boolean, default=False)
password = Column(String, nullable=False) password = Column(String, nullable=False)
refresh_token = Column(String, nullable=True)
async def init_db(): async def init_db():
async with async_engine.begin() as conn: async with async_engine.begin() as conn:
@@ -77,6 +78,13 @@ async def LoginUser(user_info):
if user and verify_password(user_info.password, user.password): if user and verify_password(user_info.password, user.password):
return user return user
return None return None
async def refresh_token(encoded_jwt, email):
async with AsyncSessionLocal() as session:
result = await session.execute(select(User).where(User.email==email))
user = result.scalar_one_or_none()
if user:
user.refresh_token = encoded_jwt
await session.commit()
async def main(): async def main():
await init_db() await init_db()
#await CreateUser() #await CreateUser()

View File

@@ -1,3 +1,6 @@
//Разобраться с хранением и использованием refresh token
//Добавить endpoint logout
const token = localStorage.getItem("token"); const token = localStorage.getItem("token");
if (token) { if (token) {
window.location.href = "./../main/index.html"; window.location.href = "./../main/index.html";
@@ -21,11 +24,16 @@ document.getElementById('loginForm').addEventListener('submit', async function (
}); });
const data = await response.json(); // читаем только один раз const data = await response.json(); // читаем только один раз
const remember_me = document.getElementById("remember").checked
if (response.ok) { if (response.ok) { // сохраняем только при успехе
localStorage.setItem("token", data.access_token); // сохраняем только при успехе if (remember_me) { // если нажат чекбокс, то сохраняем в localstorage со стандартным временем действия
localStorage.setItem("token", data.access_token);
} else { // если не нажат, то в sessionstorage до перезахода в браузер(?)
sessionStorage.setItem("token", data.access_token);
}
window.location.href = './../main/index.html'; window.location.href = './../main/index.html';
} else { } else { //парсинг и вывод ошибок, если есть
if (Array.isArray(data.detail)) { if (Array.isArray(data.detail)) {
const messages = data.detail.map(e => { const messages = data.detail.map(e => {
const field = e.loc.filter(locPart => locPart !== 'body').join(' -> '); const field = e.loc.filter(locPart => locPart !== 'body').join(' -> ');
@@ -42,7 +50,7 @@ document.getElementById('loginForm').addEventListener('submit', async function (
showError(['Connection error: ' + err.message]); showError(['Connection error: ' + err.message]);
} }
}); });
function showError(messages){ function showError(messages){ //Добавление их на form со стилями
let errorElem = document.getElementById('formError'); let errorElem = document.getElementById('formError');
let container = document.getElementById('glass-container'); let container = document.getElementById('glass-container');
if (!errorElem){ if (!errorElem){

View File

@@ -9,6 +9,9 @@
<body> <body>
<div class="glass-container"> <div class="glass-container">
<div class="data-box"> <div class="data-box">
<form id="logoutForm">
<button type="submit">Logout</button>
</form>
<h2>data</h2> <h2>data</h2>
<form action="#" method="POST"> <form action="#" method="POST">
<p>Data</p> <p>Data</p>

View File

@@ -1,4 +1,13 @@
const token = localStorage.getItem("token"); function tokenCheck(){
if (!token) { const token = localStorage.getItem("token") || sessionStorage.getItem("token");
window.location.href = "./../login/index.html"; if (!token) {
} window.location.href = "./../login/index.html";
}
}
tokenCheck()
document.getElementById('logoutForm').addEventListener('submit', async function (e) {
e.preventDefault();
localStorage.removeItem("token");
tokenCheck();
});

View File

@@ -58,3 +58,17 @@ p {
color: #fff; color: #fff;
margin-top: 15px; margin-top: 15px;
} }
button {
background: #fff;
color: black;
padding: 10px;
border: none;
border-radius: 10px;
cursor: pointer;
margin-top: 15px;
}
button:hover {
background: transparent;
color: white;
outline: 1px solid #fff;
}