jebrosen / rocket_oauth2 Goto Github PK
View Code? Open in Web Editor NEWOAuth2 for Rocket applications
License: Apache License 2.0
OAuth2 for Rocket applications
License: Apache License 2.0
Is it possible to use different redirect URLs? I'd like to have OAuth on web and mobile platforms all be redirected through my backend, however, they require different redirect URIs.
use rocket::http::{Cookies};
The above import is not working for rocket v0.5.0-rc.2. I have tried switching to CookieJar
but it seems some changes need to be made to rocket_oauth2 lib to accommodate rocket v0.5.0-rc.2 CookieJar
.
So in my application I want to be able to revoke user tokens when they log out. While I could just delete the tokens this does leave some potential security issues and leaves my app onto their authorized apps page.
I don't know much about how rfc:s and such work but revoking seems to be described in RFC7009 the process is also described in the Reddit api docs.
Now I don't know if this stuff is in spec or not and I don't know how well it's supported on other services but I would appreciate having revoking as a feature.
๐ Hi Jeb,
Just wondering - what are the plans for this crate with the upcoming Rocket v0.5 release (which I'm sure is keeping you plenty busy ๐ )?
I've used this with v0.4 and really appreciated the ergonomics of it - I'd like to use it with a new project based on the v0.5-rc1 branch of Rocket but am not sure where to get started.
If you're interested, I'd be happy to help fix some of the type errors that come up but there's probably some larger changes needed with the ability to use async (does hyper_sync_rustls
still make sense?) and the move to Figment.
First off, thanks for this great crate! :)
Rocket 0.5.0-rc.4 was just released with several breaking changes.
Relevant:
Outcome::Failure
was renamed to Outcome::Error. Cookie::build
error[E0599]: no variant or associated item named `Failure` found for enum `Outcome` in the current scope
--> /.cargo/registry/src/index.crates.io-6f17d22bba15001f/rocket_oauth2-0.5.0-rc.2/src/lib.rs:431:33
|
431 | return Outcome::Failure((
| ^^^^^^^ variant or associated item not found in `Outcome<_, (Status, _), Status>`
error[E0599]: no variant or associated item named `Failure` found for enum `Outcome` in the current scope
--> /.cargo/registry/src/index.crates.io-6f17d22bba15001f/rocket_oauth2-0.5.0-rc.2/src/lib.rs:453:33
|
453 | return Outcome::Failure((
| ^^^^^^^ variant or associated item not found in `Outcome<_, (Status, _), Status>`
error[E0599]: no variant or associated item named `Failure` found for enum `Outcome` in the current scope
--> /.cargo/registry/src/index.crates.io-6f17d22bba15001f/rocket_oauth2-0.5.0-rc.2/src/lib.rs:478:37
|
478 | return Outcome::Failure((
| ^^^^^^^ variant or associated item not found in `Outcome<_, (Status, _), Status>`
error[E0599]: no variant or associated item named `Failure` found for enum `Outcome` in the current scope
--> /.cargo/registry/src/index.crates.io-6f17d22bba15001f/rocket_oauth2-0.5.0-rc.2/src/lib.rs:511:26
|
511 | Outcome::Failure((Status::BadRequest, e))
| ^^^^^^^ variant or associated item not found in `Outcome<_, (Status, _), Status>`
error[E0061]: this function takes 1 argument but 2 arguments were supplied
--> /.cargo/registry/src/index.crates.io-6f17d22bba15001f/rocket_oauth2-0.5.0-rc.2/src/lib.rs:710:13
|
710 | Cookie::build(STATE_COOKIE_NAME, state)
| ^^^^^^^^^^^^^ -------
| | |
| | unexpected argument of type `std::string::String`
| help: remove the extra argument
|
note: associated function defined here
--> /.cargo/registry/src/index.crates.io-6f17d22bba15001f/cookie-0.18.0/src/lib.rs:324:12
|
324 | pub fn build<C: Into<Cookie<'c>>>(base: C) -> CookieBuilder<'c> {
|
[global.oauth.facebook]
provider = { auth_uri = "https://graph.facebook.com/oauth/authorize", token_uri = "https://graph.facebook.com/oauth/access_token" }
client_id = ""
client_secret = ""
redirect_uri = "http://localhost:8000/auth/facebook"
This is my Rocket.toml but I'm not sure why I'm not able to create a login flow.
Thanks for the help!
Setting this up against MS (for use against the Azure oauth2 endpoints in AuthorizationCode workflow) doesn't like the response back from the token endpoint:
Error: Token exchange failed: Error { kind: DeserializationError(Error("invalid type: string "3600", expected i32", line: 1, column: 62)) }
I think this is coming from the serde deserialize call at the end of the token exchange handler, but I can't see the response back from the endpoint to see what data it's choking on.
For reference, I've set this out as a custom Provider using the Table notation (bit of digging into the code led me to that approach):
[global.oauth.azure]
provider = { auth_uri = "https://login.microsoftonline.com/common/oauth2/authorize", token_uri = "https://login.microsoftonline.com/common/oauth2/token" }
redirect_uri = "https://localhost:8000/user/auth"
client_id = "<redacted>"
client_secret = "<redacted>"
I've then attached the fairing to my Rocket as so:
.attach(OAuth2::fairing(HyperSyncRustlsAdapter, login_azure_callback, "azure", "/user/auth", Some(("/user/login", vec!["user:read".to_string()]))))
where my login_azure_callback is:
pub fn login_azure_callback(request: &Request, token: TokenResponse) -> Result<Redirect, Box<::std::error::Error>> {
let mut cookies = request.guard::<Cookies>().expect("Cookies");
cookies.add_private(
Cookie::build("token", token.access_token)
.same_site(SameSite::Lax)
.finish()
);
Ok(Redirect::to("/"))
}
This works, in as much as obtaining the auth code works, I get the MS oauth flow, but the redirect back results in a 400 BadRequest and the error above.
I was wondering if there is any way to handle custom status codes other than 200.
I get the redirect to callback_url with something like this
https://b5f6-45-251-234-73.ngrok.io/auth/instagram?code=AQBwRfNKQFHiv-X6m0-O8M9iLDrM-uVMwDcjYSyLRzIUeXylPVhu4V7qIZcEuT37yi21QtBjhiuvBAdi1g2jo_ddS68I3bIIcEddezdbsDo8GOLrxVVxupQpPKH5Fz6WaxGEbkzfUu9nhdiLy5Pa5Ri1sFm1GksUmK9jWRfHPfS73jgiL0jO45w91EQgR5kui3vHzchklRx-H_xDh9-YIb8i9ScdJsYXJZydBNZ5s4V9cA&state=3INTGxYIbi5lBu9ROTtUXw#_
but with 400 bad request.
So my question is, Is there any simple way to handle the 400 status code ?
Hi Jeb,
I am wondering how to best implement the refresh token exchange in rocket_oauth2.
As a relative newcomer to rocket I am not sure that I would come up with a good solution.
But here are my thoughts:
Implementing the exchange function is trivial by adding a token type parameter to Adapter::exchange_token. But how to access it from a request is the question: AFAIU you cannot access a fairing from a request. So we would need to register another object as managed state!?! Does not look very elegant to me.
Do you have any ideas?
Cheers,
Christoph
The parameters to the callback in the examples and docs look like this:
#[get("/auth/microsoft")]
fn microsoft_callback(
token: TokenResponse<MicrosoftUserInfo>,
mut cookies: Cookies<'_>,
) -> Result<Redirect, Debug<Error>> {
...
}
I was playing around just now and for some reason swapped the two parameters so that the signature looked like this:
#[get("/auth/microsoft")]
fn microsoft_callback(
mut cookies: Cookies<'_>,
token: TokenResponse<MicrosoftUserInfo>,
) -> Result<Redirect, Debug<Error>> {
...
}
And when that route is hit, the following error is shown in the logs:
=> Matched: GET /auth/microsoft (microsoft_callback)
=> Error: Multiple `Cookies` instances are active at once.
=> An instance of `Cookies` must be dropped before another can be retrieved.
=> Warning: The retrieved `Cookies` instance will be empty.
Warning: The OAuth2 state returned from the server did not match the stored state.
=> Outcome: Failure
=> Warning: Responding with 400 Bad Request catcher.
=> Response succeeded.
This presumably makes sense given a bit of knowledge of Rocket's request handling, since TokenResponse
's FromRequest
impl needs mutable access the cookies and they're already borrowed mutably by mut cookies
, but it's a bit surprising - probably nothing that documentation can't manage.
When using Twitch as a provider, I get the following error:
Token exchange failed: Error { kind: DeserializationError(Error("invalid type: sequence, expected a string", line: 1, column: 146)) }.
Example data returned from Twitch:
{
"access_token": "0123456789abcdefghijABCDEFGHIJ",
"refresh_token": "eyJfaWQmNzMtNGCJ9%6VFV5LNrZFUj8oU231/3Aj",
"expires_in": 3600,
"scope": ["viewing_activity_read"],
"token_type": "bearer"
}
I suspect it may be Twitch returning an array of scopes rather than a space-separated string.
It'd be nice to pass a single URL to rocket_oauth2 to discover the token and redirection endpoints lazily. The even more general use case is complete OpenID Connect Discovery via webfinger (not covered here).
I'd expect my application to work like this:
This doesn't appear to be possible right now, because the Provider needs to be passed into rocket_oauth2 and be initialized on startup.
Maybe a solution would be to implement the Provider as a trait rather than a struct, so that the URLs could be fetched lazily if need be - auth_uri and token_uri are only needed upon the first request anyways.
This way, I could pass a custom Provider implementation that fetches the OP metadata on demand.
Cheers,
Uwe
I'm trying to implement VK OAuth2, but their API responds with JSON like this (idk why, they even put a link to OAuth specs):
{
"access_token": "533bacf01e11f55b536a565b57531ac114461ae8736d6506a3",
"expires_in": 43200,
"user_id": 66748
}
So I need a way to get an access token, but I get this error right now:
Err(
Error {
kind: ExchangeFailure,
source: Some(
"TokenResponse token_type was missing or not a string",
),
},
)
How can get around with this?
By default rocket_oauth2 doesn't work with the reddit config it provides. This is because on the exchange_code function reddit expects the client id and secret to be provided using a Authorization header. Reddit details the requirements here but basically using the default code reddit will just return a 401 because the Authorization header isn't provided
I was able to get reddit logon working by replacing lines 76-85 in hyper_sync_rustls_adapter.rs
with
//ser.append_pair("client_id", config.client_id());
//ser.append_pair("client_secret", config.client_secret());
let req_str = ser.finish();
let request = client
.post(config.provider().token_uri().as_ref())
.header(Accept::json())
.header(ContentType::form_url_encoded())
.header(Authorization(
Basic {
username: config.client_id().to_owned(),
password: Some(config.client_secret().to_owned()),
}
))
.body(&req_str);
also its impossible to get an refresh token with reddit as its api requires you to tell it during authorization whenever you want a temporary
or permanent
token using a duration
parameter in the request. I was also able to workaround this by just hardcoding the authorization request to set the duration. I think this wouldn't be a problem if issue #13 got resolved as then duration
could be sent using an custom parameter.
also unrelated the version of rocket_sync_rustls
should probably be updated as it uses an old version of rustls. This caused problems for me as i use another lib that uses a newer version of rustls and I could not compile as two versions of ring-asm
were required and as ring-asm
is a native library it can only have one compiled version. I was able to fix this by just changing the cargo.toml to use version =0.3.0-rc.17
instead of =0.3.0-rc.4
Hi, I've been using rocket_oauth2
on the side to try and implement a Slackbot with some basic functionality. It took me quite a while to figure out how specify a custom provider so I'd like to contribute back an example of how to set one up.
I already put up a draft pull request #11 based on the Slack integration I set up, but ran into some issues with the basic.identity
scope. I'd appreciate any insights on how to fix this. If it doesn't seem reasonable to fix (I don't expect anyone here to be supporting specific OAuth2 implementations!) would you mind suggesting another API we could implement to showcase how to use custom providers?
My initial goal was to provide an example as similar as possible to the existing examples/user_info_hyper_sync_rustls
so it readers would have an easy time spotting differences.
Sorry, I am rather new to OAuth and especially integrating it into Rust.
I am using Microsoft and pulling the email address from the microsoft account in /auth/microsoft
into a cookie as follows:
cookies.add_private(
Cookie::build(("email", user_info.email))
.same_site(SameSite::Lax)
.build(),
I noticed that there is a cookie on the client side.
Imagine I implement another route as follows:
#[get("/other")]
async fn other(user: User) -> String {
user.email
}
Also imagine it does something sensitive rather than returning the email address. Apart from stealing a legitimate token/email cookie. Is this secure? Is there no way someone can just impersonate someone else or simply change there email address value without obtaining a legitimate token.
E.g., can a user simply change their email cookie to some random string with a special encoding or is it secure?
My data on the server-side will most likely be tied to the users email and so I do not want the user to be able to change this without obtaining a valid token/cookie (I am currently ignoring cookie grabbing with XSS at the moment as it should not be a problem for me)
Hi! Where i can find another examples (i check github and reddit but im looking for facebook and google).
I notice that rocket_oauth2
is implemented without oauth2
which I think provide much more functionality. Why rocket_oauth2 decided to implement differently?
Hi there,
I'm currently toying around with rocket/_oauth2 and wondering about a very common auth scenario:
User...
After the last step, the expectation is that the user will return to the same route that he/she was at before (the one he/she bookmarked or let the session expire on).
OAuth2 supports this via the ?state=.., which rocket_oauth2 happily fills with its own state as it appears.
So how would I implement the above scenario?
Cheers,
Uwe
Hi Jeb,
just started using your crate.
Your code assumes that the scope parameter is returned by the token exchange.
But the RFC states that it will be transmitted with the authorization callback.
So TokenResponse.scope is always none.
I am willing to work on a patch but I am unsure how to do it:
The first one should be easy. But this field does not really belong in TokenResponse. And if we wanna add Refresh token support it would not be able to fill the scope field.
What do you think?
Cheers,
Christoph
(Sorry, this is more a question than an issue).
Thanks for your work! Pretty useful :)
What would be a recommended approach to validate the access_token
in api calls?
Hi, as far as I can see the redirect_uri
seems to be required in any case: https://github.com/jebrosen/rocket_oauth2/blob/master/src/config.rs#L13
Now in the scenario I'm using this library, which is only the token exchange, in a server-to-server scenario, providing the redirect_uri is not required (also see github's docs: https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/#parameters-1).
It would be nice if this library allowed me to omit this parameter.
In https://github.com/jebrosen/rocket_oauth2/blob/0.5.0-rc.1/src/lib.rs#L693 the rocket_oauth2_state
cookie is set. By default, Rocket's add_private
securely configures cookies with httponly(true)
, however the secure
flag is not set. Would it be possible to add a possibility to set the secure
flag on this cookie? Either automatically when a secure connection is used (not sure if this is possible), or somehow in the configuration? Or just make it the default for production builds?
I am also considering to suggest this on Rocket
as a default instead, as I think it would better to use secure defaults (at the very least for production builds).
(Split off from #11 (comment))
Some providers require or support additional parameters to be specified in the redirect request. One example is Slack, which has a second user_scope=
parameter to specify scopes for the impersonated user in addition to the standard scope=
which applies to the bot user.
This should be possible to do today by retrieving OAuth2<C>
from managed state, calling OAuth2<C>::get_redirect()
, and then manually mangling the Redirect
URI. This is an unsavory option.
Perhaps it should be easier to add custom request parameters; something similar was done for TokenResponse
(see #9 (comment)) to allow access to the exact data returned by the server. One option might be OAuth2<C>::get_redirect_custom(&self, cookies: &mut Cookies<'_>, scopes: &[&str], params: Params)
. Params
would likely be an iterator of key-value pairs.
I would appreciate any further examples of providers that would benefit or require this functionality.
EDIT(2020-08-20): Removed mention of the automatically-generated login route, which was removed in 0.3.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.