⚠️ SECURITY UPDATE ⚠️
A critical security vulnerability has been discovered in documentation versions prior to 0.4.1 Please update to the latest version immediately.
Read the security advisory
Loco OAuth2
Loco OAuth2 is a simple OAuth2 initializer for the Loco API. It is designed to be a tiny and easy-to-use library for implementing OAuth2 in your application.
Docs
Offical RFC 6749 OAuth2 documentation can be found here.
Shuttle tutorial can be found here.
What is OAuth2?
OAuth2 is a protocol that allows a user to grant a third-party website or application access to the user's protected resources, without necessarily revealing their long-term credentials or even their identity. For this to work, the user needs to authenticate with the third-party site and grant access to the client application.
Grant Types
There are several grant types in OAuth2. Currently Loco OAuth2 supports the Authorization Code Grant. Client
Credentials
Grant, Implicit Grant and more are planned for future releases.
Table of Contents
- Installation
- Feature Flags
- Glossary
- Configuration (Authorization Code Grant)
- Initialization
- Migration
- Models
- Controllers
Installation
Cargo
Or Cargo.toml
[]
= { = "0.5" }
[]
= { = true }
Feature Flags
This library supports two session management backends through feature flags:
use_tower_sessions(default) - Usestower-sessionsfor session managementaxum_session- Usesaxum-sessionfor session management
Important Limitations
⚠️ Feature Flag Conflict: These features are mutually exclusive. If both features are enabled simultaneously, the axum_session feature takes precedence over use_tower_sessions.
Usage
Default (tower-sessions):
[]
= "0.5"
With axum-session:
[]
= { = "0.5", = ["axum_session"], = false }
Explicit tower-sessions:
[]
= { = "0.5", = ["use_tower_sessions"] }
Migration Between Features
When switching between session backends, you may need to:
- Update your session store initialization code
- Update controller imports and function calls
- Ensure your database schema is compatible with the chosen backend
Glossary
OAuth2ClientGrantEnum |
Enum for the different OAuth2 grants, an OAuth2 Client will belong to one of the OAuth2ClientGrantEnum |
OAuth2ClientStore |
Abstraction implementation for managing one or more OAuth2 clients. |
authorization_code::Client |
A client that uses the Authorization Code Grant. |
Configuration (Authorization Code Grant)
Generate a private cookie secret key
secret_key is used to encrypt the private cookie jar. It must be more than 64 bytes. If not provided, it will be auto-generated. Here is an example of how to generate a private cookie secret key.
# Cargo.toml
= "0.9.0-alpha.1"
= { = "0.9.3", = ["cookie-private"]}
// src/main.rs
use Key;
use ;
OAuth2 Configuration
OAuth2 Configuration is done in the config/*.yaml file. The oauth2 section is used to configure the OAuth2 clients.
This example is using Google Cloud as the OAuth2 provider. You need a Google Cloud project and create OAuth2 credentials
for client_id and client_secret using OAuth client Id option. redirect_url is the server callback endpoint for
the provider which should set within Authorised redirect URIs section when creating OAuth2 client id.
# config/*.yaml
# Initializers Configuration
initializers:
oauth2:
secret_key: # Optional, key for Private Cookie Jar, must be more than 64 bytes
authorization_code: # Authorization code grant type
- client_identifier: google # Identifier for the OAuth2 provider. Replace 'google' with your provider's name if different, must be unique within the oauth2 config.
client_credentials:
client_id: # Replace with your OAuth2 client ID.
client_secret: # Replace with your OAuth2 client secret.
url_config:
auth_url: # authorization endpoint from the provider
token_url: # token endpoint from the provider for exchanging the authorization code for an access token
redirect_url: # server callback endpoint for the provider, for default jwt route use 'default="http://localhost:5150/api/oauth2/google/callback/cookie"'
profile_url: # user profile endpoint from the provider for getting user data
scopes:
- # Scopes for requesting access to user data
- # Scopes for requesting access to user data
cookie_config:
protected_url: # Optional for jwt - For redirecting to protect url in cookie to prevent XSS attack
timeout_seconds: 600 # Optional, default 600 seconds
Initialization
We are going to use the initializer functionality in Loco framework to initialize the OAuth2 client.
Firstly we need to create a session store for the storing the csrf token. We will use the AxumSessionStore for this
purpose. We will create a new initializer struct for AxumSessionStore and implement the Initializer trait.
# Cargo.toml
# axum sessions
= { = "0.16.0" }
// src/initializers/axum_session.rs
use async_trait;
use Router as AxumRouter;
use *;
;
We will create a new initializer struct for OAuth2ClientStore and implement the Initializer trait. In
the after_routes function, we will get the oauth2 settings from the config and create the OAuth2ClientStore and
add it to the AxumRouter as an extension.
// src/initializers/oauth2.rs
use ;
use ;
use *;
;
Do not forget to add the initializers to the App struct.
// src/app.rs
;
Migration
Installation
We need to install workspace loco-oauth2 library within the migration folder.
# migration/Cargo.toml
[]
= { = true }
Migration Script
A migration is required to create the o_auth2_sessions table for the OAuth2ClientStore.
The o_auth2_sessions table is used to connect the user to the OAuth2 session, and it is used to store session data.
Instead of creating the table by hand, you can import the migration script from loco_oauth2 into your migration
folder.
// migration/src/lib.rs
use migration;
pub use *;
;
Here shape of the o_auth2_sessions table.
CREATE TABLE o_auth2_sessions
(
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
id SERIAL PRIMARY KEY,
session_id VARCHAR NOT NULL,
expires_at TIMESTAMP NOT NULL,
user_id INTEGER NOT NULL
CONSTRAINT "fk-sessions-users"
REFERENCES users
ON UPDATE CASCADE
ON DELETE CASCADE
);
Run migration using the following command.
Then generate all the models using the following command.
Models
The user details shape returns from the OAuth2 Provider depends on the scope of your settings. Here is an example of the user model for Google OAuth2 provider. You can change the fields based on what you requested. For more information on scopes, see Google Scopes
// src/models/user.rs
/// `OAuth2UserProfile` user profile information via scopes
/// https://developers.google.com/identity/openid-connect/openid-connect#obtainuserinfo
Next we need to implement 2 traits for the users::Model model and the o_auth2_sessions::Model.
OAuth2UserTrait Example
We should generate a random password for the user and save it to the database.
# Cargo.toml
[]
= "3"
// src/models/users.rs
use OAuth2UserTrait;
use ;
use o_auth2_sessions;
use async_trait;
use Local;
use PasswordGenerator;
OAuth2SessionsTrait Example
// src/models/o_auth2_sessions.rs
pub use ;
use users;
use async_trait;
use Local;
use ;
use *;
use *;
Controllers
We need to implement 3 controllers for the OAuth2 flow.
authorization_url - This controller is used to get the authorization URL to redirect the user to the OAuth2 provider.
callback - This controller is used to handle the callback from the OAuth2 provider. We can use either return a PrivateCookieJar(which redirects to protected route) or a JWT token.
protected - This controller is used to protect the route from unauthorized access.
OAuth2Controller Example
// src/controllers/oauth2.rs
use SessionNullPool;
use get_authorization_url;
use OAuth2ClientStore;
use crate;
/// The authorization URL for the `OAuth2` flow
/// This will redirect the user to the `OAuth2` provider's login page
/// and then to the callback URL
/// # Arguments
/// * `session` - The axum session
/// * `oauth_store` - The `OAuth2ClientStore` extension
/// # Returns
/// The HTML response with the link to the `OAuth2` provider's login page
/// # Errors
/// `loco_rs::errors::Error` - When the `OAuth2` client cannot be retrieved
pub async
CallbackController Cookie Example
use SessionNullPool;
use callback;
use OAuth2ClientStore;
use crate;
// src/controllers/oauth2.rs
/// The callback URL for the `OAuth2` flow
/// This will exchange the code for a token and then get the user profile
/// then upsert the user and the session and set the token in a short live
/// cookie Lastly, it will redirect the user to the protected URL
/// # Arguments
/// * `ctx` - The application context
/// * `session` - The axum session
/// * `params` - The query parameters
/// * `jar` - The oauth2 private cookie jar
/// * `oauth_store` - The `OAuth2ClientStore` extension
/// # Returns
/// The response with the short live cookie and the redirect to the protected
/// URL
/// # Errors
/// * `loco_rs::errors::Error`
pub async
CallbackController JWT Example - SPA applications
/// The callback URL for the `OAuth2` flow
/// This will exchange the code for a token and then get the user profile
/// then upsert the user and the session and set the token in a short live
/// cookie Lastly, it will redirect the user to the protected URL
/// # Generics
/// * `T` - The user profile, should implement `DeserializeOwned` and `Send`
/// * `U` - The user model, should implement `OAuth2UserTrait` and `ModelTrait`
/// * `V` - The session model, should implement `OAuth2SessionsTrait` and `ModelTrait`
/// * `W` - The database pool
/// # Arguments
/// * `ctx` - The application context
/// * `session` - The axum session
/// * `params` - The query parameters
/// * `oauth2_store` - The `OAuth2ClientStore` extension
/// # Return
/// * `Result<impl IntoResponse>` - The response with the jwt token
/// # Errors
/// * `loco_rs::errors::Error`
pub async
ProtectedController Example
// src/controllers/oauth2.rs
use *;
use crate::;
async
Google boilerplate Example - Cookie
Since we are using Google OAuth2 provider, there is a google boilerplate code to get the authorization URL and the callback cookie.
// src/controllers/oauth2.rs
use SessionNullPool;
use ;
use *;
use crate::;
async
Google boilerplate Example - JWT
Since we are using Google OAuth2 provider, there is a google boilerplate code to get the authorization URL and the callback jwt.
// src/controllers/oauth2.rs
use SessionNullPool;
use ;
use *;
use crate::;