# 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