# RouteKit Classic ASP - MVC Starter A clean starting point for building Classic ASP applications with the RouteKit MVC framework. ## Quick Setup 1. Copy this folder to your IIS server 2. Point your IIS site root to the `public/` folder 3. Update `public/web.config`: - Set `ConnectionString` to your database path - Set `ErrorLogPath` if you want file logging 4. Ensure the IIS URL Rewrite module is installed 5. Browse to `http://localhost/` — you should see the welcome page ## Keycloak Authentication The core Keycloak helper is loaded automatically from `core/lib.Keycloak.asp`. It uses Keycloak's OpenID Connect authorization-code flow to redirect users to Keycloak, exchange the callback code for tokens, and fetch user profile data from the `userinfo` endpoint. ### Configure `public/web.config` Update these `appSettings` before enabling login: ```xml ``` - `KeycloakBaseUrl`: Base URL of the Keycloak server, without `/realms/...`. - `KeycloakRealm`: Realm that owns the application client. - `KeycloakClientId`: Client ID configured in Keycloak. - `KeycloakClientSecret`: Secret for confidential clients. Leave blank for public clients. - `KeycloakRedirectUri`: Absolute callback URL in this ASP app. - `KeycloakLogoutRedirectUri`: Absolute URL to return to after Keycloak logout. - `KeycloakScope`: OIDC scopes to request. The default is `openid profile email`. - `KeycloakPendingLoginCookieMinutes`: How long the temporary login state and nonce cookie should survive during the redirect round-trip. - `KeycloakAllowedClockSkewSeconds`: Grace period for `exp`, `nbf`, and `iat` validation when checking the ID token claims. - `KeycloakHttp*TimeoutMs`: Outbound HTTP timeouts for the token and userinfo requests. - `KeycloakEnableLogging` / `KeycloakLogPath`: Optional diagnostic logging for Keycloak request and token-validation failures. Keep `KeycloakClientSecret` out of source control and inject it per environment. Use HTTPS callback and logout URLs outside local development. ### Configure the Keycloak client In Keycloak, create or update a client for this app: - Client protocol: OpenID Connect. - Access type/client authentication: use a confidential client if you set `KeycloakClientSecret`; otherwise use a public client. - Valid redirect URIs: include the exact `KeycloakRedirectUri`, for example `http://localhost/auth/callback`. - Valid post logout redirect URIs: include the exact `KeycloakLogoutRedirectUri`. - Web origins: include the app origin, for example `http://localhost`, or configure according to your environment policy. ### Use the helper in controllers Add routes in `public/Default.asp` for login, callback, and logout actions. In those controller actions, call the helper functions: ```asp ' Login action Call KeycloakLogin() ' Callback action If KeycloakHandleCallback() Then Response.Redirect KeycloakConsumePostLoginRedirectPath("/") Else Response.Write H(KeycloakAuth().ErrorMessage) End If ' Logout action Call KeycloakLogout("") ``` After login, use the current user and token helpers anywhere after core autoload: ```asp If KeycloakIsLoggedIn() Then Dim user Set user = KeycloakCurrentUser() If Not user Is Nothing Then Response.Write H(user.Item("preferred_username")) End If End If Dim accessToken accessToken = KeycloakAccessToken() ``` To protect a controller action and return the user to the original page after sign-in: ```asp If Not KeycloakRequireLogin("") Then Exit Sub If Not KeycloakHasRealmRole("admin") Then Response.Status = "403 Forbidden" Response.Write "Forbidden" Exit Sub End If ``` Available helper functions: - `KeycloakLogin()`: Redirects to Keycloak and stores temporary login state for the redirect round-trip. - `KeycloakHandleCallback()`: Validates callback state and nonce, exchanges the code, stores tokens, and fetches user info. - `KeycloakIsLoggedIn()`: Returns True when an access token is in Session. - `KeycloakCurrentUser()`: Returns the cached userinfo dictionary, or ID token claims when userinfo is unavailable. - `KeycloakUserInfo()`: Calls Keycloak's `userinfo` endpoint with the current access token. - `KeycloakAccessToken()`, `KeycloakRefreshToken()`, `KeycloakIdToken()`: Return stored tokens. - `KeycloakTokenClaims(token)`: Decodes JWT payload claims into a dictionary. - `KeycloakRequireLogin(returnToPath)`: Redirects unauthenticated users to login and preserves a safe relative return path. - `KeycloakConsumePostLoginRedirectPath(fallbackPath)`: Returns the stored post-login destination, then clears it from Session. - `KeycloakHasRealmRole(roleName)`: Returns True when the stored ID token includes the named realm role. - `KeycloakHasClientRole(clientId, roleName)`: Returns True when the stored ID token includes the named client role. - `KeycloakLogoutUrl(postLogoutRedirectUri)`: Builds a Keycloak logout URL. - `KeycloakLogout(postLogoutRedirectUri)`: Clears Session values and redirects to Keycloak logout. ### Session values The helper stores tokens and user info in Session using the `Keycloak_` prefix. Use HTTPS in production so tokens are protected in transit, and configure IIS session settings according to your application's security requirements. ## Project Structure ``` MVC-Starter/ public/ # IIS ROOT - point your IIS site here Default.asp # Front controller (entry point) web.config # IIS config, routes, connection strings core/ # Framework core (do not modify) autoload_core.asp # Loads all core libraries router.wsc # Route matching engine mvc.asp # MVC dispatcher lib.*.asp # Core libraries app/ controllers/ # Your controllers go here views/ # Your views go here shared/ # Shared layout (header, footer) models/ # POBOs go here repositories/ # Repository classes go here db/ migrations/ # Database migrations webdata.accdb # Access database scripts/ # Code generators generateController.vbs generateMigration.vbs GenerateRepo.vbs runMigrations.vbs ``` ## Adding a New Feature ### 1. Generate a migration ```bash cscript //nologo scripts\generateMigration.vbs create_my_table ``` ### 2. Generate POBO and Repository ```bash cscript //nologo scripts\GenerateRepo.vbs /table:my_table /pk:id ``` Move generated files to `app/models/` and `app/repositories/`. ### 3. Generate a controller ```bash cscript //nologo scripts\generateController.vbs MyController "Index;Show(id);Create;Store" ``` Move generated file to `app/controllers/`. ### 4. Wire it up - Register in `core/lib.ControllerRegistry.asp` - Include in `app/controllers/autoload_controllers.asp` - Add routes in `public/Default.asp` - Create views in `app/views/MyController/` ## Included Controllers - **HomeController** - Welcome page at `/` - **ErrorController** - 404 handler at `/404` ## Requirements - Windows Server with IIS - Classic ASP enabled - IIS URL Rewrite module - Microsoft Access Database Engine (for .accdb support) ## Testing This repo now includes a dev-only `aspunit` harness under `tests/`. It is intentionally separate from the production app rooted at `public/`. - Configure a separate IIS application rooted at `tests/` - Ensure Classic ASP parent paths are enabled for that IIS app - If you change `tests/web.config`, refresh the nested test-folder copies with `cscript //nologo tests\sync-webconfigs.vbs` - Open `run-all.asp` inside that IIS app to execute the test suite - See `TESTING.md` for setup, manifest registration, and extension guidance