Use JWT token for access token

This commit is contained in:
Pierre HUBERT 2022-04-15 20:08:31 +02:00
parent 69bb2816b9
commit 94c601119a
4 changed files with 77 additions and 37 deletions

View File

@ -2,9 +2,14 @@ use actix::{Actor, AsyncContext, Context, Handler};
use actix::Message; use actix::Message;
use crate::constants::*; use crate::constants::*;
use crate::data::access_token::AccessToken;
use crate::data::app_config::AppConfig;
use crate::data::client::ClientID; use crate::data::client::ClientID;
use crate::data::code_challenge::CodeChallenge; use crate::data::code_challenge::CodeChallenge;
use crate::data::jwt_signer::JWTSigner;
use crate::data::user::UserID; use crate::data::user::UserID;
use crate::utils::err::Res;
use crate::utils::string_utils::rand_str;
use crate::utils::time::time; use crate::utils::time::time;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)] #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
@ -20,9 +25,8 @@ pub struct Session {
pub authorization_code: String, pub authorization_code: String,
pub authorization_code_expire_at: u64, pub authorization_code_expire_at: u64,
pub authorization_code_used: bool,
pub access_token: String, pub access_token: Option<String>,
pub access_token_expire_at: u64, pub access_token_expire_at: u64,
pub refresh_token: String, pub refresh_token: String,
pub refresh_token_expire_at: u64, pub refresh_token_expire_at: u64,
@ -36,6 +40,26 @@ impl Session {
self.authorization_code_expire_at < time() && self.access_token_expire_at < time() self.authorization_code_expire_at < time() && self.access_token_expire_at < time()
&& self.refresh_token_expire_at < time() && self.refresh_token_expire_at < time()
} }
pub fn regenerate_access_and_refresh_tokens(&mut self,
app_config: &AppConfig,
jwt_signer: &JWTSigner) -> Res {
let access_token = AccessToken {
issuer: app_config.website_origin.to_string(),
subject_identifier: self.user.clone(),
issued_at: time(),
exp_time: time() + OPEN_ID_ACCESS_TOKEN_TIMEOUT,
rand_val: rand_str(OPEN_ID_ACCESS_TOKEN_LEN),
nonce: self.nonce.clone(),
};
self.access_token_expire_at = access_token.exp_time;
self.access_token = Some(jwt_signer.sign_token(access_token.to_jwt_claims())?);
self.refresh_token = rand_str(OPEN_ID_REFRESH_TOKEN_LEN);
self.refresh_token_expire_at = OPEN_ID_REFRESH_TOKEN_TIMEOUT + time();
Ok(())
}
} }
#[derive(Message)] #[derive(Message)]
@ -54,10 +78,6 @@ pub struct FindSessionByRefreshToken(pub String);
#[rtype(result = "Option<Session>")] #[rtype(result = "Option<Session>")]
pub struct FindSessionByAccessToken(pub String); pub struct FindSessionByAccessToken(pub String);
#[derive(Message)]
#[rtype(result = "()")]
pub struct MarkAuthorizationCodeUsed(pub String);
#[derive(Message)] #[derive(Message)]
#[rtype(result = "()")] #[rtype(result = "()")]
pub struct UpdateSession(pub Session); pub struct UpdateSession(pub Session);
@ -121,23 +141,11 @@ impl Handler<FindSessionByAccessToken> for OpenIDSessionsActor {
fn handle(&mut self, msg: FindSessionByAccessToken, _ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: FindSessionByAccessToken, _ctx: &mut Self::Context) -> Self::Result {
self.session self.session
.iter() .iter()
.find(|f| f.access_token.eq(&msg.0)) .find(|f| f.access_token.as_ref().map(|t| t.eq(&msg.0)).unwrap_or(false))
.cloned() .cloned()
} }
} }
impl Handler<MarkAuthorizationCodeUsed> for OpenIDSessionsActor {
type Result = ();
fn handle(&mut self, msg: MarkAuthorizationCodeUsed, _ctx: &mut Self::Context) -> Self::Result {
if let Some(r) = self.session
.iter_mut()
.find(|f| f.authorization_code.eq(&msg.0)) {
r.authorization_code_used = true;
}
}
}
impl Handler<UpdateSession> for OpenIDSessionsActor { impl Handler<UpdateSession> for OpenIDSessionsActor {
type Result = (); type Result = ();

View File

@ -9,7 +9,7 @@ use askama::Template;
use crate::actors::{openid_sessions_actor, users_actor}; use crate::actors::{openid_sessions_actor, users_actor};
use crate::actors::openid_sessions_actor::{OpenIDSessionsActor, Session, SessionID}; use crate::actors::openid_sessions_actor::{OpenIDSessionsActor, Session, SessionID};
use crate::actors::users_actor::UsersActor; use crate::actors::users_actor::UsersActor;
use crate::constants::{AUTHORIZE_URI, CERT_URI, OPEN_ID_ACCESS_TOKEN_LEN, OPEN_ID_ACCESS_TOKEN_TIMEOUT, OPEN_ID_AUTHORIZATION_CODE_LEN, OPEN_ID_AUTHORIZATION_CODE_TIMEOUT, OPEN_ID_REFRESH_TOKEN_LEN, OPEN_ID_REFRESH_TOKEN_TIMEOUT, OPEN_ID_SESSION_LEN, TOKEN_URI, USERINFO_URI}; use crate::constants::*;
use crate::controllers::base_controller::FatalErrorPage; use crate::controllers::base_controller::FatalErrorPage;
use crate::data::app_config::AppConfig; use crate::data::app_config::AppConfig;
use crate::data::client::{ClientID, ClientManager}; use crate::data::client::{ClientID, ClientManager};
@ -139,11 +139,10 @@ pub async fn authorize(user: CurrentUser, id: Identity, query: web::Query<Author
redirect_uri, redirect_uri,
authorization_code: rand_str(OPEN_ID_AUTHORIZATION_CODE_LEN), authorization_code: rand_str(OPEN_ID_AUTHORIZATION_CODE_LEN),
authorization_code_expire_at: time() + OPEN_ID_AUTHORIZATION_CODE_TIMEOUT, authorization_code_expire_at: time() + OPEN_ID_AUTHORIZATION_CODE_TIMEOUT,
authorization_code_used: false, access_token: None,
access_token: rand_str(OPEN_ID_ACCESS_TOKEN_LEN),
access_token_expire_at: time() + OPEN_ID_ACCESS_TOKEN_TIMEOUT, access_token_expire_at: time() + OPEN_ID_ACCESS_TOKEN_TIMEOUT,
refresh_token: rand_str(OPEN_ID_REFRESH_TOKEN_LEN), refresh_token: "".to_string(),
refresh_token_expire_at: time() + OPEN_ID_REFRESH_TOKEN_TIMEOUT, refresh_token_expire_at: 0,
nonce: query.0.nonce, nonce: query.0.nonce,
code_challenge, code_challenge,
}; };
@ -272,7 +271,7 @@ pub async fn token(req: HttpRequest,
&query.authorization_code_query, &query.authorization_code_query,
&query.refresh_token_query) { &query.refresh_token_query) {
("authorization_code", Some(q), _) => { ("authorization_code", Some(q), _) => {
let session: Session = match sessions let mut session: Session = match sessions
.send(openid_sessions_actor::FindSessionByAuthorizationCode(q.code.clone())) .send(openid_sessions_actor::FindSessionByAuthorizationCode(q.code.clone()))
.await.unwrap() .await.unwrap()
{ {
@ -310,17 +309,18 @@ pub async fn token(req: HttpRequest,
} }
} }
if session.authorization_code_used { if let Some(_) = session.access_token {
return Ok(error_response(&query, "invalid_request", "Authorization code already used!")); return Ok(error_response(&query, "invalid_request", "Authorization code already used!"));
} }
// Mark session as used session.regenerate_access_and_refresh_tokens(&app_config, &jwt_signer)?;
sessions.send(openid_sessions_actor::MarkAuthorizationCodeUsed(session.authorization_code))
sessions.send(openid_sessions_actor::UpdateSession(session.clone()))
.await.unwrap(); .await.unwrap();
// Generate id token // Generate id token
let token = IdToken { let id_token = IdToken {
issuer: app_config.website_origin.to_string(), issuer: app_config.website_origin.to_string(),
subject_identifier: session.user, subject_identifier: session.user,
audience: session.client.0.to_string(), audience: session.client.0.to_string(),
@ -331,11 +331,11 @@ pub async fn token(req: HttpRequest,
}; };
TokenResponse { TokenResponse {
access_token: session.access_token, access_token: session.access_token.expect("Missing access token!"),
token_type: "Bearer", token_type: "Bearer",
refresh_token: session.refresh_token, refresh_token: session.refresh_token,
expires_in: session.access_token_expire_at - time(), expires_in: session.access_token_expire_at - time(),
id_token: Some(jwt_signer.sign_token(token.to_jwt_claims())?), id_token: Some(jwt_signer.sign_token(id_token.to_jwt_claims())?),
} }
} }
@ -358,17 +358,14 @@ pub async fn token(req: HttpRequest,
return Ok(error_response(&query, "access_denied", "Refresh token has expired!")); return Ok(error_response(&query, "access_denied", "Refresh token has expired!"));
} }
session.refresh_token = rand_str(OPEN_ID_REFRESH_TOKEN_LEN); session.regenerate_access_and_refresh_tokens(&app_config, &jwt_signer)?;
session.refresh_token_expire_at = OPEN_ID_REFRESH_TOKEN_TIMEOUT + time();
session.access_token = rand_str(OPEN_ID_ACCESS_TOKEN_LEN);
session.access_token_expire_at = OPEN_ID_ACCESS_TOKEN_TIMEOUT + time();
sessions sessions
.send(openid_sessions_actor::UpdateSession(session.clone())) .send(openid_sessions_actor::UpdateSession(session.clone()))
.await.unwrap(); .await.unwrap();
TokenResponse { TokenResponse {
access_token: session.access_token, access_token: session.access_token.expect("Missing access token!"),
token_type: "Bearer", token_type: "Bearer",
refresh_token: session.refresh_token, refresh_token: session.refresh_token,
expires_in: session.access_token_expire_at - time(), expires_in: session.access_token_expire_at - time(),

34
src/data/access_token.rs Normal file
View File

@ -0,0 +1,34 @@
use jwt_simple::claims::JWTClaims;
use jwt_simple::prelude::Duration;
pub struct AccessToken {
pub issuer: String,
pub subject_identifier: String,
pub issued_at: u64,
pub exp_time: u64,
pub rand_val: String,
pub nonce: Option<String>,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct CustomAccessTokenClaims {
rand_val: String,
}
impl AccessToken {
pub fn to_jwt_claims(self) -> JWTClaims<CustomAccessTokenClaims> {
JWTClaims {
issued_at: Some(Duration::from_secs(self.issued_at)),
expires_at: Some(Duration::from_secs(self.exp_time)),
invalid_before: None,
issuer: Some(self.issuer),
subject: Some(self.subject_identifier),
audiences: None,
jwt_id: None,
nonce: self.nonce,
custom: CustomAccessTokenClaims {
rand_val: self.rand_val
},
}
}
}

View File

@ -10,3 +10,4 @@ pub mod jwt_signer;
pub mod id_token; pub mod id_token;
pub mod code_challenge; pub mod code_challenge;
pub mod open_id_user_info; pub mod open_id_user_info;
pub mod access_token;