Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to handle authentication #96

Closed
manifest opened this issue Sep 3, 2018 · 12 comments
Closed

How to handle authentication #96

manifest opened this issue Sep 3, 2018 · 12 comments

Comments

@manifest
Copy link
Contributor

manifest commented Sep 3, 2018

In our applications we use JSON Web Tokens in authorization header and in some cases an access_token parameter in query string to authenticate user requests. Since some resources may allow an anonymous access, while other restrict it to users with particular permissions, we need a way to determine if token was presented in a request and if it's presented – validate and extract an information from it (for instance, sub claim in our tokens contains a user identifier).

Is there already any concept how it could be implemented?

@carllerche
Copy link
Owner

One day, there would be a more "out of the box" strategy (maybe you can investigate it).

The strategy would be for you to have a User struct, representing the authenticated user. You would then implement Extract for it.

In your handler, you would do:

#[get("/protected")]
fn my_action(&self, user: User) -> ... { }

You have access to the request via extract::Context.

Now, for more advanced cases, you would have to have different User types indicating the capabilities. I'm not exactly sure what flexibility you need, so it is hard for me to propose an exact solution. One way might be using generics.

struct User<C: Capability> {
    capability: C,
}

trait Capability {
    // Whatever you need to authenticate a user
}

impl<C: Capability> Extract for User<C> {
    type Future = ExtractUserFuture<C>;

    fn extract(context: &Context) -> Self::Future {
        C::method_used_for_validating_capability(context);
    }
}

In this case, you would be using the type system to enforce capabilities. You can get as crazy as you want...

fn action(&self, user: User<(CreateFoo, DeleteFoo)>) { }

@manifest
Copy link
Contributor Author

manifest commented Sep 3, 2018

In order to validate a JWT I need a secret. It is stored in a configuration file of an application or it can be loaded from a database in some cases. What would be a convenient way to access it from within the extract ?

#[derive(Debug)]
struct Resource {
    secret: Secret,
}

#[derive(Debug)]
struct User {
    id: &'static str,
}

impl<B: BufStream> Extract<B> for User {
    type Future = Immediate<User>;

    fn extract(context: &Context) -> Self::Future {
        info!("{:?}", context);
        Immediate::ok(User { id: "123" })
    }
}

impl_web! {
    impl Resource {
        #[get("/")]
        fn action(&self, user: User) {
            ...
        }
    }
}

@carllerche
Copy link
Owner

carllerche commented Sep 3, 2018

My plan has been to enable configuration through the context argument. This has not been implemented yet. Either you can try to take a stab at it, or I can try to get it done sometime this week or so.

Configuration will have to be stored using an "any map" strategy. This would be similar to: https://github.com/hyperium/http/blob/master/src/extensions.rs

The ServiceBuilder would have a way to store these configuration values. The values would need to be Sync and the "any map" would be stored in an Arc. At that point, it can be passed into the resource handler and eventually to the extract function.

Then, from extract, you could do:

context.config.get::<MyConfigType>()

@manifest
Copy link
Contributor Author

manifest commented Sep 4, 2018

@carllerche Please, take a look. Is that what you had in mind?

  • I've created a module config. If it shouldn't be there, just say where to move it.
  • I've changed dispatch method of Resource trait to pass config as its 5th argument
  • I use context.config::<State>() instead of context.config.get::<State>() because Config is read only and it seems like the only get method will be available. If it is wrong, I will change it back.

@manifest
Copy link
Contributor Author

manifest commented Sep 4, 2018

Here is an application example

struct State {
    secret: String,
}

struct Resource;
struct User;

impl<B: BufStream> Extract<B> for User {
    type Future = Immediate<User>;

    fn extract(context: &Context) -> Self::Future {
        let state = context.config::<State>().unwrap();
        ...
    }
}

impl_web! {
    impl Resource {
        #[get("/")]
        fn action(&self, user: User) {
            ...
        }
    }
}

fn main() {
    ...
    ServiceBuilder::new()
        .config(State { secret: "secret".to_owned() })
        .resource(Resource)
        .run(&addr)
        .unwrap();
}

@manifest
Copy link
Contributor Author

manifest commented Sep 4, 2018

It looks like there are some errors for rustc 1.26.0. Do we need workarounds?

@carllerche
Copy link
Owner

Ideally, I believe that it would just require a manual fmt::Debug implementation.

@manifest
Copy link
Contributor Author

manifest commented Sep 4, 2018

Ok. Will do. What else should be added / changed?

@carllerche
Copy link
Owner

@manifest I left some inline comments. I'm still thinking about ServiceBuilder but I don't have any immediate thoughts.

@mmrath
Copy link

mmrath commented Sep 5, 2018

@carllerche while the user extraction methods works, it is going to be very verbose if most of the routes need to be authenticated but no use of user param. Probably a better way is to provide something like a filter. https://tomcat.apache.org/tomcat-9.0-doc/servletapi/javax/servlet/Filter.html

@manifest
Copy link
Contributor Author

manifest commented Sep 5, 2018

@mmrath I'm not familiar with Java. Could you please elaborate on how filter differs from middleware? Could you provide an example how it may look like in Rust?

@carllerche
Copy link
Owner

@mmrath I would be happy to continue that discussion in a new issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants