Dual-licensed under MIT
or the UNLICENSE.
Implements OpenID Connect Core 1.0 and OpenID Connect Discovery 1.0.
Implements UMA2 - User Managed Access, an extension to OIDC/OAuth2. Use feature flag uma2
to enable this feature.
It supports Microsoft OIDC with feature microsoft
. This adds methods for authentication and token validation, those skip issuer check.
This library is a quick and dirty rewrite of inth-oauth2 and oidc to use async / await. Basic idea was to solve particular task, as result most of good ideas from original crates were perverted and over-simplified.
Using reqwest for the HTTP client and biscuit for Javascript Object Signing and Encryption (JOSE).
Add dependency to Cargo.toml:
toml
[dependencies]
openid = "0.7"
This example provides only Rust part, assuming just default JHipster frontend settings.
Cargo.toml:
```toml [package] name = 'openid-example' version = '0.1.0' authors = ['Alexander Korolev kilork@yandex.ru'] edition = '2018'
[dependencies] actix = '0.9' actix-identity = '0.2' actix-rt = '1.0' exitfailure = "0.5" uuid = { version = "0.8", features = [ "v4" ] } url = "2.1" openid = "0.7"
[dependencies.serde] version = '1.0' features = ['derive']
[dependencies.reqwest] version = '0.10' features = ['json']
[dependencies.actix-web] version = '2.0' features = ['rustls'] ```
src/main.rs:
```rust
extern crate actix_web;
use actix::prelude::*; use actixidentity::{CookieIdentityPolicy, Identity, IdentityService}; use actixweb::{ dev::Payload, error::ErrorUnauthorized, http, middleware, web, App, Error, FromRequest, HttpRequest, HttpResponse, HttpServer, Responder, }; use exitfailure::ExitFailure; use openid::{DiscoveredClient, Options, Token, Userinfo}; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, pin::Pin, sync::RwLock}; use url::Url;
struct User {
id: String,
login: Option
struct Logout {
idtoken: String,
logouturl: Option
impl FromRequest for User {
type Config = ();
type Error = Error;
type Future = Pin
fn from_request(req: &HttpRequest, pl: &mut Payload) -> Self::Future {
let fut = Identity::from_request(req, pl);
let sessions: Option<&web::Data<RwLock<Sessions>>> = req.app_data();
if sessions.is_none() {
eprintln!("sessions is none!");
return Box::pin(async { Err(ErrorUnauthorized("unauthorized")) });
}
let sessions = sessions.unwrap().clone();
Box::pin(async move {
if let Some(identity) = fut.await?.identity() {
if let Some(user) = sessions
.read()
.unwrap()
.map
.get(&identity)
.map(|x| x.0.clone())
{
return Ok(user);
}
};
Err(ErrorUnauthorized("unauthorized"))
})
}
}
struct Sessions {
map: HashMap struct Failure {
error: String,
} async fn authorize(oidcclient: web::Data } async fn account(user: User) -> impl Responder {
web::Json(user)
} struct LoginQuery {
code: String,
} async fn requesttoken(
oidcclient: web::Data } async fn login(
oidc_client: web::Data } async fn logout(
oidc_client: web::Data } fn host(path: &str) -> String {
"http://localhost:9000".to_string() + path
} async fn main() -> Result<(), ExitFailure> {
let clientid = " }
``` See full example: openid-example[derive(Serialize, Deserialize, Debug)]
[get("/oauth2/authorization/oidc")]
eprintln!("authorize: {}", auth_url);
HttpResponse::Found()
.header(http::header::LOCATION, auth_url.to_string())
.finish()
[get("/account")]
[derive(Deserialize, Debug)]
eprintln!("user info: {:?}", userinfo);
Ok(Some((token, userinfo)))
[get("/login/oauth2/code/oidc")]
match request_token(oidc_client, query).await {
Ok(Some((token, userinfo))) => {
let id = uuid::Uuid::new_v4().to_string();
let login = userinfo.preferred_username.clone();
let email = userinfo.email.clone();
let user = User {
id: userinfo.sub.clone().unwrap_or_default(),
login,
last_name: userinfo.family_name.clone(),
first_name: userinfo.name.clone(),
email,
activated: userinfo.email_verified,
image_url: userinfo.picture.clone().map(|x| x.to_string()),
lang_key: Some("en".to_string()),
authorities: vec!["ROLE_USER".to_string()], //FIXME: read from token
};
identity.remember(id.clone());
sessions
.write()
.unwrap()
.map
.insert(id, (user, token, userinfo));
HttpResponse::Found()
.header(http::header::LOCATION, host("/"))
.finish()
}
Ok(None) => {
eprintln!("login error in call: no id_token found");
HttpResponse::Unauthorized().finish()
}
Err(err) => {
eprintln!("login error in call: {:?}", err);
HttpResponse::Unauthorized().finish()
}
}
[post("/logout")]
let id_token = token.bearer.access_token.into();
let logout_url = oidc_client.config().end_session_endpoint.clone();
return HttpResponse::Ok().json(Logout {
id_token,
logout_url,
});
}
}
HttpResponse::Unauthorized().finish()
[actix_rt::main]
eprintln!("discovered config: {:?}", client.config());
let client = web::Data::new(client);
let sessions = web::Data::new(RwLock::new(Sessions {
map: HashMap::new(),
}));
HttpServer::new(move || {
App::new()
.wrap(middleware::Logger::default())
.wrap(IdentityService::new(
CookieIdentityPolicy::new(&[0; 32])
.name("auth-openid")
.secure(false),
))
.app_data(client.clone())
.app_data(sessions.clone())
.service(authorize)
.service(login)
.service(web::scope("/api").service(account).service(logout))
})
.bind("localhost:8080")?
.run()
.await?;
Ok(())