1 Commits

Author SHA1 Message Date
Pierre HUBERT
7391a4f488 feat: can add custom claims to access token
All checks were successful
continuous-integration/drone/push Build is passing
2026-02-24 15:17:40 +01:00
5 changed files with 62 additions and 16 deletions

View File

@@ -39,6 +39,11 @@ You can configure a list of clients (Relying Parties) in a `clients.yaml` file w
claims_id_token:
groups: ["group_{user}"]
service: "auth"
# Optional, claims to be added to the access token payload
# The placeholders of `claims_id_token` can also be used here
claims_access_token:
groups: ["group_{user}"]
service: "auth"
# Optional, claims to be added to the user info endpoint response
# The placeholders of `claims_id_token` can also be used here
claims_user_info:

View File

@@ -4,10 +4,10 @@ use actix::{Actor, AsyncContext, Context, Handler};
use crate::constants::*;
use crate::data::access_token::AccessToken;
use crate::data::app_config::AppConfig;
use crate::data::client::ClientID;
use crate::data::client::{Client, ClientID};
use crate::data::code_challenge::CodeChallenge;
use crate::data::jwt_signer::JWTSigner;
use crate::data::user::UserID;
use crate::data::user::{User, UserID};
use crate::utils::err::Res;
use crate::utils::string_utils::rand_str;
use crate::utils::time_utils::time;
@@ -50,10 +50,13 @@ impl Session {
&mut self,
app_config: &AppConfig,
jwt_signer: &JWTSigner,
user: &User,
client: &Client,
) -> Res {
let access_token = AccessToken {
issuer: app_config.website_origin.to_string(),
subject_identifier: self.user.clone().0,
user,
client,
issued_at: time(),
exp_time: time() + OPEN_ID_ACCESS_TOKEN_TIMEOUT,
rand_val: rand_str(OPEN_ID_ACCESS_TOKEN_LEN),

View File

@@ -501,13 +501,7 @@ pub async fn token(
));
}
session.regenerate_access_and_refresh_tokens(AppConfig::get(), &jwt_signer)?;
sessions
.send(openid_sessions_actor::UpdateSession(session.clone()))
.await
.unwrap();
// Get user information
let user: Option<User> = users
.send(users_actor::GetUserRequest(session.user.clone()))
.await
@@ -518,6 +512,18 @@ pub async fn token(
Some(u) => u,
};
// Refresh access and refresh tokens
session.regenerate_access_and_refresh_tokens(
AppConfig::get(),
&jwt_signer,
&user,
&client,
)?;
sessions
.send(openid_sessions_actor::UpdateSession(session.clone()))
.await
.unwrap();
// Generate id token
let id_token = IdToken {
issuer: AppConfig::get().website_origin.to_string(),
@@ -574,8 +580,24 @@ pub async fn token(
));
}
session.regenerate_access_and_refresh_tokens(AppConfig::get(), &jwt_signer)?;
// Get user information
let user: Option<User> = users
.send(users_actor::GetUserRequest(session.user.clone()))
.await
.unwrap()
.0;
let user = match user {
None => return Ok(error_response(&query, "invalid_request", "User not found!")),
Some(u) => u,
};
// Regenerate user session
session.regenerate_access_and_refresh_tokens(
AppConfig::get(),
&jwt_signer,
&user,
&client,
)?;
sessions
.send(openid_sessions_actor::UpdateSession(session.clone()))
.await

View File

@@ -1,9 +1,12 @@
use crate::data::client::{AdditionalClaims, Client};
use crate::data::user::User;
use jwt_simple::claims::JWTClaims;
use jwt_simple::prelude::Duration;
pub struct AccessToken {
pub struct AccessToken<'a> {
pub issuer: String,
pub subject_identifier: String,
pub user: &'a User,
pub client: &'a Client,
pub issued_at: u64,
pub exp_time: u64,
pub rand_val: String,
@@ -13,21 +16,26 @@ pub struct AccessToken {
#[derive(serde::Serialize, serde::Deserialize)]
pub struct CustomAccessTokenClaims {
rand_val: String,
/// Additional claims
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(flatten)]
pub additional_claims: Option<AdditionalClaims>,
}
impl AccessToken {
impl<'a> AccessToken<'a> {
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),
subject: Some(self.user.uid.0.to_string()),
audiences: None,
jwt_id: None,
nonce: self.nonce,
custom: CustomAccessTokenClaims {
rand_val: self.rand_val,
additional_claims: self.client.claims_access_token(self.user),
},
}
}

View File

@@ -42,6 +42,9 @@ pub struct Client {
/// Additional claims to return with the id token
claims_id_token: Option<AdditionalClaims>,
/// Additional claims to return with the access token
claims_access_token: Option<AdditionalClaims>,
/// Additional claims to return through the user info endpoint
claims_user_info: Option<AdditionalClaims>,
}
@@ -117,6 +120,11 @@ impl Client {
self.process_additional_claims(user, &self.claims_id_token)
}
/// Get additional claims for access_token for a successful authentication
pub fn claims_access_token(&self, user: &User) -> Option<AdditionalClaims> {
self.process_additional_claims(user, &self.claims_access_token)
}
/// Get additional claims for user info for a successful authentication
pub fn claims_user_info(&self, user: &User) -> Option<AdditionalClaims> {
self.process_additional_claims(user, &self.claims_user_info)