rrrodzilla / rusty_paseto

A type-driven, ergonomic RUST implementation of the PASETO protocol for secure stateless tokens.
https://crates.io/crates/rusty_paseto
MIT License
71 stars 9 forks source link

Beginner question #25

Closed eratio08 closed 2 years ago

eratio08 commented 2 years ago

Hey hey,

really nice implementation and an amazing documentation!

I'm new to rust and my problem might just stem from the fact that I do not know how to archive this the rust-way.

I'm trying to set a subject claim from a non-static reference/dynamic value in a trait implementation.

let token = PasetoBuilder::<V4, Local>::default()
  .set_claim(SubjectClaim::from(&id))
  .build(&key)
  .unwrap();

From what I understand the signature of set_claim required the argument to have a 'static lifetime which will not be possible for a dynamic value (I could be wrong).

How could this be archived?

rrrodzilla commented 2 years ago

Eike,

Thanks for the kind words. I sincerely apologize for the late reply. I'm in the middle of moving my family and a job search. Can you provide a full code example showing the issue?

Thanks and again, I apologize for the delay.

Best regards,

Roland

eratio08 commented 2 years ago

Hey Roland,

thanks for you reply!

So I'm trying to implement the IdentityPolicy Trait of actix_identity to store a paseto token in a Cookie.

pub struct PasetoCookieIdentityPolicy {}

impl IdentityPolicy for PasetoCookieIdentityPolicy {
    type Future = Ready<Result<Option<String>, Error>>;
    type ResponseFuture = Ready<Result<(), Error>>;

    fn from_request(&self, request: &mut ServiceRequest) -> Self::Future {
        ready(Ok({
            request
                .cookie(&EnvVar::TokenCookieName.get_expect())
                .and_then(|cookie| {
                    let key = PasetoSymmetricKey::<V4, Local>::from(
                        request.app_data::<AppData>().unwrap().paseto_key.clone(),
                    );

                    let token: &str = cookie.value();
                    PasetoParser::<V4, Local>::default()
                        .parse(token, &key)
                        .ok()
                        .map(|token| token["sup"].to_string())
                })
        }))
    }

    fn to_response<B>(
        &self,
        identity: Option<String>,
        changed: bool,
        response: &mut ServiceResponse<B>,
    ) -> Self::ResponseFuture {
        if changed {
            let id = match identity {
                None => return ready(Ok(())),
                Some(id) => id,
            };
            let key = PasetoSymmetricKey::<V4, Local>::from(
                response
                    .request()
                    .app_data::<AppData>()
                    .unwrap()
                    .paseto_key
                    .clone(),
            );

            let claim = SubjectClaim::from(SubjectClaim::from(&id[..])); <-- failing here because string slice of id can not have a 'static lifetime
            let token = PasetoBuilder::<V4, Local>::default()
                .set_claim(claim)
                .build(&key)
                .unwrap();

            let mut cookie = Cookie::new(EnvVar::TokenCookieName.get_expect().to_owned(), &token);
            cookie.set_expires(OffsetDateTime::now_utc() + Duration::hours(1));
            cookie.set_secure(true);
            cookie.set_http_only(true);
            cookie.set_same_site(SameSite::Strict);

            let val = HeaderValue::from_str(&cookie.to_string()).unwrap();
            response.headers_mut().append(header::SET_COOKIE, val);
        }

        ready(Ok(()))
    }
}

Will try to get the repo up for a full code example if this snippet is not sufficient.

rrrodzilla commented 2 years ago

So the simple solution here is to turn your string into a & 'static str like so:

let id : &'static str = Box::leak(identity.into_boxed_str());
let claim = SubjectClaim::from(id);

However I don't recommend building a token for every request in a policy like this. I'd suggest using a middleware to validate an existing token (or you could create a policy that only validates).

The reason for this is because encrypting a token is IO heavy on the CPU. If you attempt to encrypt a token on every single request you'll rapidly get bogged down. Instead, I recommend creating a token when the user has been authenticated and storing that in a cookie (or passing it to a client to be used in subsequent requests) so that the policy or middleware can be used to validate the token on each request which is a much quicker process.

I've added an example to the repository which does this. However, I think a cleaner solution would be to use a middleware instead of a policy but I've created it as a policy since that's what you're attempting to do. Good luck and have fun!

eratio08 commented 2 years ago

Thank you for the advice and the great example!

eratio08 commented 2 years ago

Is the usage of https://doc.rust-lang.org/std/boxed/struct.Box.html#method.leak save? From the description it sounds like using it will cause a memory leak as the box will never be released.

Was not planning to create a token on each request, but only to it on the login case, need to figure out how this is signaled to the policy, possibly with the changed flag.

rrrodzilla commented 2 years ago

Yes, I should have been more clear. Box::leak is safe, however, as indicated in the name, it leaks the memory which is what you would expect when you make a value live as 'static since it will live for the life of the program. Given that it looks like you're writing a server, this is probably not what you want to do if you're going to be calling this repeatedly. The correct way would for me to just get rid of the need for the value to live for 'static so I've done that and uploaded version 0.4.0 and updated the example so you no longer need to use Box::leak and your original implementation should now work. Thanks!