wasm-service-oauth

Use OAuth with Cloudflare Workers

Examples below have been tested with Github. It should work with other oauth providers with minor changes.

Setup

The configuration parameters are passed in an OAuthConfig struct to initialize the service. The example below sets the secret parameters in the environment, so they aren't part of the compiled wasm binary.

  1. Create an environment variable env_json containing json data, which will be parsed by the worker.

At the bottom of wrangler.toml, add the following.

```toml [vars]

envjson= """{ "oauth": { "appurl": "https://app.example.com/", "authorizedusers": [ "gituser" ], "clientid" : "0000", "clientsecret": "0000", "statesecret": "0000", "corsorigins": [ "https://app.example.com", "http://localhost:3000" ], "loggedoutappurl": "https://app.example.com/", "sessionpathprefix": "/private/", "session_secret": "0000" } } ```

  1. Update your service program as follows

```rust2018

[wasm_bindgen]

extern "C" { static env_json: String; }

pub async fn mainentry(req: Jsvalue) -> Result { // ... let environconfig = envjson.asstr(); let settings = load(environconfig).maperr(|e| JsValue::from_str(&e))?; // ...

let oauth_config = build_oauth_config(&settings.oauth)?;
let oauth_handler = OAuthHandler::init(oauth_config)
    .map_err(|e| JsValue::from(&format!("OAuthHandler init error: {}", e.to_string())))?;


wasm_service::service_request(
    req,
    ServiceConfig {
        logger, 
        handlers: vec![
            Box::new(MyHandler(oauth_handler))
        ],
        ..Default::default()
    }
).await

}

fn buildoauthconfig(env: &Oauth) -> Result { let allow = wasmserviceoauth::UserAllowList { allowedusers: env.authorizedusers.clone(), loginfailedurl: "/login-failed".into(), };

let config = OAuthConfig {
    app_url: env.app_url.to_string(),
    logged_out_app_url: env.logged_out_app_url.to_string(),
    authorize_url_path: "/authorize".to_string(),
    code_url_path: "/code".to_string(),
    login_failed_url_path: "/login-failed".to_string(),
    logout_url_path: "/logout".to_string(),
    auth_checker: Box::new(allow),
    client_id: env.client_id.to_string(),
    client_secret: env.client_secret.to_string(),
    state_secret: key_from_hex(&env.state_secret, 32).map_err(JsValue::from)?,
    session_secret: key_from_hex(&env.session_secret, 32).map_err(JsValue::from)?,
    session_cookie_path_prefix: env.session_path_prefix.to_string(),
    cors_origins: env.cors_origins.clone(), // .iter().map(|v| v.as_ref()).collect(),
    ..Default::default()
};
Ok(config)

}

/// load config from environment pub(crate) fn load(json: &str) -> Result { //let var = std::env::var("envjson") // .maperr(|| Error::Environment("Missing envjson".tostring()))?; let conf = serdejson::fromstr(json).maperr(|e| e.to_string())?; Ok(conf) }

[derive(Debug, Deserialize)]

pub struct Config { pub oauth: Oauth, }

[derive(Debug, Deserialize)]

pub struct Oauth { pub clientid: String, pub clientsecret: String, pub statesecret: String, pub sessionsecret: String, pub sessionpathprefix: String, pub appurl: String, pub loggedoutappurl: String, pub corsorigins: Vec, pub authorizedusers: Vec, } ```

  1. Update the handler function as follows ```rust2018 async fn handle(&self, req: &Request, mut ctx: &mut Context) -> Result<(), HandlerReturn> {

    // urls beginning with sessionpathprefix require authentication if req.url().path().startswith("/private/") { let _session = self.oauthhandler.verifyauthuser(req, &mut ctx)?; // user is authenticated!! // ...

    } else { // handle urls not requiring authentication // ... }

    // let oauth handler process its urls if ctx.response().isunset() { if self.oauthhandler.wouldhandle(&req) { // handle oauth processing for /code, /authorize, /login-failed, etc. self.oauthhandler.handle(req, &mut ctx).await?; } } Ok(()) } ```