Giter Club home page Giter Club logo

Comments (5)

Mythra avatar Mythra commented on June 9, 2024

Hi @Sirneij ! Thanks for the issue, I can say this repository is currently not actively maintained unfortunately. You'll want to head to one of the maintained libraries (which you can find at https://paseto.io). I personally recommend: https://github.com/brycx/pasetors (i think they did a great job and it's why I didn't update this one to v3/v4).

That being said I can help answer this, invalidation will need to be handled someway in your application. If your expirey mechanism is time only you can be aided with the helper functions in builder that let you specify expiry time or by manually specifying the exp claim. If you're using paseto claims with public keys of users you could rotate those keys. Or, finally you could have a custom claim has a field, that you can use to provide a key that you can use for expiration. I imagine the final one will be most useful!

Wishing you luck! Thanks!

from paseto.

Sirneij avatar Sirneij commented on June 9, 2024

The token generated has three fields: id, exp, and nbf. The tokens expire after 24 hours. My concern is how I can invalidate these tokens before expiry, like when a user logs out. How best can I do this. I built my token using the following snippet:

...
pub fn issue_token(id: Uuid) -> String {
    let current_date_time = Utc::now();
    let dt = current_date_time + chrono::Duration::days(1);
    paseto::tokens::PasetoBuilder::new()
        .set_encryption_key(&Vec::from(SETTINGS.secret.secret_key.as_bytes()))
        .set_expiration(&dt)
        .set_not_before(&Utc::now())
        .set_claim("id", serde_json::json!(id))
        .build()
        .expect("Failed to construct paseto token w/ builder!")
}
...

And I am verifying them using:

...
pub async fn verify_token(token: String) -> Result<Session, BackendError> {
    let token = paseto::tokens::validate_local_token(
        &token,
        None,
        &SETTINGS.secret.secret_key.as_bytes(),
        &paseto::tokens::TimeBackend::Chrono,
    )
    .map_err(|e| BackendError::CannotDecryptToken(format!("Paseto: {}", e)))?;

    serde_json::from_value::<Session>(token)
        .map_err(|e| BackendError::CannotDecryptToken(format!("Serde_json: {}", e)))
}
...

from paseto.

Mythra avatar Mythra commented on June 9, 2024

Hi @Sirneij ,

As mentioned if you have some custom invalidation logic you'll want to introduce a custom claim that you can manage. The simplest possible way to do this would be just generating a random value that you store somewhere to validate if a session is live. It might also be worthwhile to while you're at it add in a field to invalidate "all" sessions if a user wishes to log out of all devices.

Now, where you store this doesn't really matter I'll write an example of just logging out a single device using pseudo-code with redis, but you don't have to use that. I recommend probably using whatever data store you already have to store user information. Although it is in the "hot-path", needing to be called everytime you'd like to validate a session, so maybe it'd be more worthwhile to use some data-store like redis that can cache these things really well. You'll also need to decide what to do when that data-store is down -- presumably fail so a user can always be sure when they "log out" and it succeeds cause the data store was up at one time, even if it goes down the user will still be "logged out", but your application may have it's own needs.

The newer version may look ~something like:

use hex;
use rand::{RngCore, OsRng};
use redis::{Commands, Connection};

/// Store the session key prefix as a const so it can't be typo'd anywhere it's used.
const SESSION_KEY_PREFIX: &str = "valid_session_key_for_{}";

pub fn issue_token(
  id: Uuid,
  redis_connection: &mut Connection,
) -> Result<String, BackendError> {
    // I just generate 128 bytes of random data for the session key
    // from something that is cryptographically secure (rand::CryptoRng)
    //
    // You don't necessarily need a random value, but you'll want something
    // that is sufficiently not able to be guessed (you don't want someone getting
    // an old token that is supposed to not be live, and being able to get a valid
    // token from that).
    let session_key: String = {
      let mut buff = [0_u8; 128];
      OsRng.fill_bytes(&mut buff);
      hex::encode(buff)
    };
    let _ = redis_connection.set(
      format!("{}{}", SESSION_KEY_PREFIX, session_key),
      // I just want to validate that the key exists to indicate the session is "live".
      String::new(),
      // "expirey" isn't needed for the key in the data store the token itself has it
      // but since redis has this feature built-in and we know a session can't last more
      // than 24 hours, why pay for storing it if it can't be used?
      //
      // plus it may help us if we ever forget to add an `exp` key to the token
      // itself due to a bug.
    ).exp(DAY_AS_SECONDS).map_err(BackendError::RedisError)?;
    let current_date_time = Utc::now();
    let dt = current_date_time + chrono::Duration::days(1);
    paseto::tokens::PasetoBuilder::new()
        .set_encryption_key(&Vec::from(SETTINGS.secret.secret_key.as_bytes()))
        .set_expiration(&dt)
        .set_not_before(&Utc::now())
        .set_claim("id", serde_json::json!(id))
        // We need the session key to validate the user hasn't logged out.
        .set_claim("session_key", serde_json::json!(session_key))
        .build()
        .map_err(BackendError::PasetoEncodeError)
}

Then on logout you would remove the key:

pub fn logout(
  session: Session,
  redis_connection: &mut Connection,
) -> Result<(), BackendError> {
   redis_connection
     .del(format!("{}{}", SESSION_KEY_PREFIX, session.session_key))
     .map_err(BackendError::RedisError)?;
   // ... whatever i would need to do for logging out.
}

Then in your "verify_token" path, validate that the user hasn't logged out by checking the key exists:

pub fn verify_token(token: String, redis_connection: &mut Connection) -> Result<Session, BackendError> {
    let token = paseto::tokens::validate_local_token(
        &token,
        None,
        &SETTINGS.secret.secret_key.as_bytes(),
        &paseto::tokens::TimeBackend::Chrono,
    )
    .map_err(|e| BackendError::CannotDecryptToken(format!("Paseto: {}", e)))?;
    let session = serde_json::from_value::<Session>(token)
        .map_err(|e| BackendError::CannotDecryptToken(format!("Serde_json: {}", e)))?;
    // Now we check that the key is still present!
    if redis_connection
      .get::<_, _, Option<String>>(
        format!("{}{}", SESSION_KEY_PREFIX, session.session_key)
      ).map_err(BackendError::RedisError)?.is_none() {
      return Err(BackendError::TokenLoggedOut);
    }
    return Ok(session);
}

from paseto.

Sirneij avatar Sirneij commented on June 9, 2024

Wow! Thank you. Let me check them out.

from paseto.

Sirneij avatar Sirneij commented on June 9, 2024

I finally added redis but as recommended, I opted for the more maintained crate, pasetors. The logic you suggested here was used heavily to invalidate tokens. Thank you.

from paseto.

Related Issues (12)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.