Skip to content

Commit

Permalink
Merge branch 'master' into provider/microsoft
Browse files Browse the repository at this point in the history
  • Loading branch information
rano-oss authored May 9, 2024
2 parents c4db064 + 5473829 commit 8bb947a
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 4 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @torto
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,9 @@ name = "discord"

[[example]]
name = "twitter"

[[example]]
name = "facebook"

[[example]]
name = "spotify"
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ To use it, it's very simple. Just create a new instance of some provider:
- TwitterProvider
- GoogleProvider
- MicrosoftProvider
- FacebookProvider
- SpotifyProvider

in your project, pass to the `new` function:

- **client_id:** Unique ID from the app created in your provider
- **secret_id:** Secret token from your app inside the provider, this token needs to be hidden from the users
- **redirect_url:** URL from your backend that will accept the return from the provider

If you are using **`CustomProvider`** you need to pass:

- **auth_url:** URL from your provider that is used to get the permission of your app access user account
Expand All @@ -37,15 +39,15 @@ This step is important because that will generate the VERIFIER field, it is need

### 2. Callback URL

After the user accepts the auth from the provider, it will redirect the user to the specific URL that you added in the config of the provider ``redirect_url``, and is important to remember that the same URL should be set in the oauth-axum params, if it is not the same an error will happen.
After the user accepts the auth from the provider, it will redirect the user to the specific URL that you added in the config of the provider `redirect_url`, and is important to remember that the same URL should be set in the oauth-axum params, if it is not the same an error will happen.
This redirect will have two query parameters, CODE and STATE, we need to generate a token from the code and verifier fields, which is the reason that in the first step, you need to save the verifier and state together.
After that, you will have a token to access the API in the provider.

## Example

This method is for a small project that will run in one unique instance of Axum. It saves the state and verifier in memory, which can be accessible in the callback URL call.

```rust
```rust
mod utils;
use std::sync::Arc;

Expand Down
69 changes: 69 additions & 0 deletions examples/facebook.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
mod utils;
use std::sync::Arc;

use axum::extract::Query;
use axum::Router;
use axum::{routing::get, Extension};
use oauth_axum::providers::facebook::FacebookProvider;
use oauth_axum::{CustomProvider, OAuthClient};

use crate::utils::memory_db_util::AxumState;

#[derive(Clone, serde::Deserialize)]
pub struct QueryAxumCallback {
pub code: String,
pub state: String,
}

#[tokio::main]
async fn main() {
dotenv::from_filename("examples/.env").ok();
println!("Starting server...");

let state = Arc::new(AxumState::new());
let app = Router::new()
.route("/", get(create_url))
.route("/api/v1/facebook/callback", get(callback))
.layer(Extension(state.clone()));

println!("🚀 Server started successfully");
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}

fn get_client() -> CustomProvider {
FacebookProvider::new(
std::env::var("FACEBOOK_CLIENT_ID").expect("FACEBOOK_CLIENT_ID must be set"),
std::env::var("FACEBOOK_SECRET").expect("FACEBOOK_SECRET must be set"),
"http://localhost:3000/api/v1/facebook/callback".to_string(),
)
}

pub async fn create_url(Extension(state): Extension<Arc<AxumState>>) -> String {
let state_oauth = get_client()
.generate_url(
Vec::from(["public_profile".to_string(), "email".to_string()]),
|state_e| async move {
state.set(state_e.state, state_e.verifier);
},
)
.await
.unwrap()
.state
.unwrap();

state_oauth.url_generated.unwrap()
}

pub async fn callback(
Extension(state): Extension<Arc<AxumState>>,
Query(queries): Query<QueryAxumCallback>,
) -> String {
println!("{:?}", state.clone().get_all_items());
let item = state.get(queries.state.clone());
get_client()
.generate_token(queries.code, item.unwrap())
.await
}
72 changes: 72 additions & 0 deletions examples/spotify.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
mod utils;
use std::sync::Arc;

use axum::extract::Query;
use axum::Router;
use axum::{routing::get, Extension};
use oauth_axum::providers::spotify::SpotifyProvider;
use oauth_axum::{CustomProvider, OAuthClient};

use crate::utils::memory_db_util::AxumState;

#[derive(Clone, serde::Deserialize)]
pub struct QueryAxumCallback {
pub code: String,
pub state: String,
}

#[tokio::main]
async fn main() {
dotenv::from_filename("examples/.env").ok();
println!("Starting server...");

let state = Arc::new(AxumState::new());
let app = Router::new()
.route("/", get(create_url))
.route("/api/v1/spotify/callback", get(callback))
.layer(Extension(state.clone()));

println!("🚀 Server started successfully");
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}

fn get_client() -> CustomProvider {
SpotifyProvider::new(
std::env::var("SPOTIFY_CLIENT_ID").expect("SPOTIFY_CLIENT_ID must be set"),
std::env::var("SPOTIFY_SECRET").expect("SPOTIFY_SECRET must be set"),
"http://localhost:3000/api/v1/spotify/callback".to_string(),
)
}

pub async fn create_url(Extension(state): Extension<Arc<AxumState>>) -> String {
let state_oauth = get_client()
.generate_url(
Vec::from([
"user-read-email".to_string(),
"user-read-private".to_string(),
]),
|state_e| async move {
state.set(state_e.state, state_e.verifier);
},
)
.await
.unwrap()
.state
.unwrap();

state_oauth.url_generated.unwrap()
}

pub async fn callback(
Extension(state): Extension<Arc<AxumState>>,
Query(queries): Query<QueryAxumCallback>,
) -> String {
println!("{:?}", state.clone().get_all_items());
let item = state.get(queries.state.clone());
get_client()
.generate_token(queries.code, item.unwrap())
.await
}
4 changes: 3 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
//! - TwitterProvider
//! - GoogleProvider
//! - MicrosoftProvider
//! - FacebookProvider
//! - SpotifyProvider
//!
//! in your project, pass to the ```new``` function:
//!
//! - **client_id:** Unique ID from the app created in your provider
//! - **secret_id:** Secret token from your app inside the provider, this token needs to be hidden from the users
//! - **redirect_url:** URL from your backend that will accept the return from the provider
//!
//!
//! If you are using **``CustomProvider``** you need to pass:
//!
//! - **auth_url:** URL from your provider that is used to get the permission of your app access user account
Expand Down
15 changes: 15 additions & 0 deletions src/providers/facebook.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use crate::CustomProvider;

pub struct FacebookProvider {}

impl FacebookProvider {
pub fn new(client_id: String, client_secret: String, redirect_url: String) -> CustomProvider {
CustomProvider::new(
String::from("https://www.facebook.com/v19.0/dialog/oauth"),
String::from("https://graph.facebook.com/v19.0/oauth/access_token"),
client_id,
client_secret,
redirect_url,
)
}
}
2 changes: 2 additions & 0 deletions src/providers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
pub mod discord;
pub mod facebook;
pub mod github;
pub mod google;
pub mod microsoft;
pub mod spotify;
pub mod twitter;
15 changes: 15 additions & 0 deletions src/providers/spotify.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use crate::CustomProvider;

pub struct SpotifyProvider {}

impl SpotifyProvider {
pub fn new(client_id: String, client_secret: String, redirect_url: String) -> CustomProvider {
CustomProvider::new(
String::from("https://accounts.spotify.com/authorize"),
String::from("https://accounts.spotify.com/api/token"),
client_id,
client_secret,
redirect_url,
)
}
}

0 comments on commit 8bb947a

Please sign in to comment.