Introduction
In this post, I'll explain how to develop an authentication API in Rust using the gRPC protocol. The authentication will
be based on JWT tokens. We'll also create a docker file to deploy the application in a docker container. Lastly, I'll
provide instructions on how to deploy the app.
Features
- User SignUp
- User login
- Password reset
- OTP verification
- Password Hashing
Project setup
Create a new project and open it in vs code editor
cargo new auth_api && cd auth_api && code .
Add the following dependencies to the Cargo.toml
file:
[package]
name = "auth_api"
version = "0.1.0"
edition = "2021"
[dependencies]
tokio = { version = "1.0", features = ["full"] }
tonic = "0.12.2"
tonic-reflection = "0.12.2"
prost = "0.13.2"
prost-types = "0.13.2"
jsonwebtoken = "9.3.0"
chrono = { version = "0.4.38", features = ["serde"] }
[build-dependencies]
tonic-build = "0.12.2"
Permission claims
I will use JSON Web Tokens for authentication, which will include custom claims to assign granted permissions to a
user. The perms
and req_perms
claims will contain permission strings like "CreateAccount", "ApiAccess", "
ResetPassword", and "DeleteAccount".
SignUp account feature
In this section, I'll explain how to implement the signup feature.
A new InitiateSignUp
endpoint is required to begin the signup process. This endpoint will generate an OTP code and a
temporary token. The user will receive the temporary token in the response and an OTP code via email. The user must then
submit the OTP code to verify their email address. Upon confirmation of the email address, the user will be permitted to
create a new account.
The following describes the flow of the signup process:
The user initiates a request to sign up for a new account. The server generates an OTP code and sends this to
user's email then the server responds with a temporary token.
This token includes thereq_perms
claim, detailing the permissions the user seeks, in this case, the permission to
create a new account ieCreateAccount
.In the subsequent request, the user submits the valid OTP together with the temporary token to confirm their email
address. Upon receiving the OTP code and the temporary token, the server validates the OTP. If it is correct, the
server creates a new temporary token with aperms
claim and moves all permissions from thereq_perms
to
theperms
claim. The server then sends a response with this new temporary token.Finally, the user can create a new account by sending their details along with the temporary token.
Proto definition
Now let's create the required rpc methods for creating account feature.
create a new file auth.proto
in the proto
directory and add the following code:
// `/proto/auth.proto`
syntax = "proto3";
package tutorial.api.auth;
service AuthService {
rpc InitiateSignUp(InitiateSignUpRequest) returns (InitiateSignUpResponse);
rpc CreateAccount(CreateAccountRequest) returns (CreateAccountResponse);
rpc VerifyOtp(VerifyOtpRequest) returns (VerifyOtpResponse);
}
message InitiateSignUpRequest {
string email = 1;
}
message InitiateSignUpResponse {
bool success = 1;
string message = 2;
string token = 3;
}
message CreateAccountRequest {
string email = 1;
string password = 2;
string first_name = 3;
string last_name = 4;
}
message CreateAccountResponse {
bool success = 1;
string message = 2;
string token = 3;
User user = 4;
}
message VerifyOtpRequest {
string token = 1;
string otp = 2;
}
message VerifyOtpResponse {
bool success = 1;
string message = 2;
string token = 3;
}
message User {
string username = 1;
string email = 2;
string full_name = 3;
Gender gender = 4;
google.protobuf.Timestamp birth_date = 5;
google.protobuf.Timestamp created_at = 6;
enum Gender {
MALE = 0;
FEMALE = 1;
OTHER = 2;
}
}
Compiling the proto file
Create a build.rs file in root of the project and add the following code:
// `/build.rs`
use std::{env, error::Error, path::PathBuf};
fn main() -> Result<(), Box<dyn Error>> {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
// define the service descriptor path for the service reflection
let service_descriptor = out_dir.join("service_descriptor.bin");
tonic_build::configure()
// this is necessary for the grpc reflection
.file_descriptor_set_path(service_descriptor)
// compile the auth.proto
.compile(&["proto/auth.proto"], &["proto"])?;
Ok(())
}
Service Implementation
Write a nested module according to the package name in the auth.proto file.
mod tutorial {
pub mod api {
pub mod auth {
// include the compiled code by package name
tonic::include_proto!("tutorial.api.auth");
// include the service descriptor set for the reflection service
pub(crate) const FILE_DESCRIPTOR_SET: &[u8] = tonic::include_file_descriptor_set!("service_descriptor");
}
}
}
Now add the following code the main function
use std::error::Error;
use tonic::transport::Server;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let addr = "0.0.0.0:50051".parse().unwrap();
println!("Running server on {addr}");
let reflection = tonic_reflection::server::Builder::configure()
.register_encoded_file_descriptor_set(tutorial::api::auth::FILE_DESCRIPTOR_SET)
.build_v1alpha()
.unwrap();
Server::builder()
.add_service(reflection)
.serve(addr)
.await?;
Ok(())
}
Now if you build and run the application and test the server using Postman It will show you available services by reflection. You don't need to provide any proto file. But for now, It does not implement the proto services.
I will discuss this topic in a future post 🔮. For now, please share your interests.
Top comments (0)