API System
The starter includes an API for programmatic access to your application. Laravel Passport issues its credentials as OAuth 2.0 access tokens, and the starter adds what an API in production needs around them: IP allowlisting, secret expiry and reminders, request logging, rate limits and analytics.
Default API Routes
Section titled “Default API Routes”The starter ships with a minimal set of API endpoints. They live in routes/api.php and can be removed or extended as needed.
- POST
/oauth/token- Exchange a client ID and secret for an access token (Passport) - GET
/api/v1/me- Returns whoever the access token acts as: the API user for a service client, or the person for a personal access token or an application - GET
/api/health- Health check results. It takes no bearer token and isn’t affected byAPI_ENABLED; it requires anX-Secret-Tokenheader matchingHEALTH_SECRET_TOKENand returns 403 for every request until that is set
No endpoint creates credentials. Service clients and OAuth applications are registered in the administration panel, and personal access tokens are created on the Account page.
Passport also serves GET /oauth/authorize, where a person approves an application on the consent screen, and POST /oauth/token/refresh, which refreshes the transient tokens Passport’s own JavaScript uses and which the starter doesn’t use. A refused request’s WWW-Authenticate header names the realm {APP_NAME} API (api.auth_realm).
The API Specification documents the endpoints under /api and how to authenticate with each kind of credential.
Core Concepts
Section titled “Core Concepts”API Users
Section titled “API Users”An API User represents an integration: a machine actor, separate from the people who sign in.
- Never signs in - API users have no way to sign in to either panel.
- Scoped authority - They can only be assigned API Integration roles, so they are never granted permissions beyond what an integration needs.
Service Clients
Section titled “Service Clients”Each API user owns one or more service clients: OAuth clients for the client credentials grant. An integration exchanges a client’s ID and secret for an access token, then sends the access token with each request. Every access token acts as the client’s API user, with its roles, so the integration’s permissions are managed on the API user.
- Short-lived access tokens - Access tokens last one hour. The integration requests a new one when it expires.
- Expiring secrets - Every client secret expires, at most one year after it is created. Reminders go to the API user’s contact email beforehand.
- IP allowlists - Each client can have its own list of allowed IP addresses and CIDR ranges.
- Rotation without downtime - Rotating creates a replacement client while the old one keeps working.
Issue a separate client for each system that calls your API, even when they act as the same API user. A compromised secret then affects one integration, each can be rotated on its own, and the request logs show which client made each request.
Authentication Flow
Section titled “Authentication Flow”-
Client creation
An administrator creates an API user, or opens an existing one, and creates a service client on its Service Clients tab: a name, a secret expiration and, optionally, allowed IP addresses.
-
Credential delivery
The client ID and secret are displayed once. Copy and store the secret securely: it is stored hashed and cannot be retrieved later.
-
Getting an access token
The integration exchanges the client ID and secret for an access token:
POST /oauth/token HTTP/1.1Host: your-app.northwestern.eduContent-Type: application/x-www-form-urlencodedgrant_type=client_credentials&client_id=019a...&client_secret=...The response contains the
access_tokenand its lifetime in seconds,expires_in. -
Calling the API
GET /api/v1/me HTTP/1.1Host: your-app.northwestern.eduAuthorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9... -
Validation
AuthenticatePassportToken, which extends chassis’sAuthenticatesPassportTokens, checks the token’s signature, expiry and revocation through Passport. It then refuses a client that has been revoked, whose secret has expired, or that is calling from an IP address its allowlist doesn’t include. It also refuses an API user that has been deleted.It then authenticates the request as the client’s API user. It does not check roles: controllers and policies authorize each action against the API user’s permissions.
-
Logging
Once the response is built,
LogsApiRequestswrites the request details to the database for monitoring and analytics, including requests that were refused and why.
Through Apigee
Section titled “Through Apigee”Most Northwestern integrations reach the application through an Apigee API proxy, which works with the University’s API Service Registry to manage access approvals and consumer onboarding. Store the client ID and secret in Apigee’s Key Value Maps, and have the proxy request an access token, cache it until it expires, and forward it to the application. To give each downstream consumer its own permissions and audit trail, create an API user and client per consumer and resolve the right credentials from the caller’s Apigee App.
Rotating and Revoking
Section titled “Rotating and Revoking”Rotate a client to create a replacement with a new ID and secret. The old client keeps working, so the integration can switch without downtime; revoke the old client once it uses the replacement. Revoking a client also revokes every access token it holds, so the integration loses access immediately rather than when its current token expires.
Personal Access Tokens
Section titled “Personal Access Tokens”People who hold the Create Personal Access Tokens permission can call the API as themselves from their own code and tools. Of the default roles, only Super Administrator has it. They create tokens on the Account area’s Access Tokens page:
- Scopes - A token can do only what its scopes name, chosen from the API-relevant permissions the person holds, and never more than the person’s permissions allow.
- Lifetime - Each token chooses 30, 90 (the default), 180 or 365 days, up to
API_PERSONAL_ACCESS_TOKEN_MAX_LIFETIME_DAYS. Tokens can’t be renewed; create a new one. - Limit - A person holds at most
API_PERSONAL_ACCESS_TOKEN_MAX_ACTIVEworking tokens (10 by default). - Shown once - The token is shown once, after it is created, and never stored in plain text.
- Reminders - People are emailed 30, 14, 7, 3 and 1 days before a token expires (
api.personal_access_tokens.expiration_notifications.intervals), unless they turn it off on their Preferences page.
A token stops working as soon as its owner loses the permission, is deactivated or is deleted; oauth:revoke-ineligible, hourly in production, marks such tokens revoked. An administrator impersonating someone can see their tokens but can’t create or revoke them. Administrators revoke a person’s token, for example one that leaked, on the person’s Personal Access Tokens tab in the administration panel, which records it in the person’s audit history.
Passport issues personal access tokens through a personal access client, which the starter creates the first time someone creates a token.
Protecting an Endpoint with a Scope
Section titled “Protecting an Endpoint with a Scope”Require a scope on each route a person’s token may reach, after the authentication middleware:
Route::get('users', UserIndexController::class)->middleware(RequireApiScope::class . ':view-users');RequireApiScope refuses a person’s token without the scope with a 403. A service client acts as its API user, whose roles are its ceiling, so it holds every scope. Controllers and policies still authorize each action against the user’s permissions.
On API requests the super-administrator shortcut in Gate::before doesn’t apply: a super administrator’s token is limited to its scopes and their permissions like anyone else’s.
Connected Applications
Section titled “Connected Applications”An external application, such as a reporting tool or a portal, can act for a person who approves it, through the OAuth 2.0 authorization code flow. AI clients connect the same way, to the MCP server instead of the REST API: they register themselves, and their tokens are refused here.
Registering an Application
Section titled “Registering an Application”Administrators register applications under API → Applications in the administration panel:
- Redirect URIs - Where people return after approving: HTTPS, or HTTP to localhost for applications on the user’s own computer.
- Allowed scopes - The most the application may request. The consent screen lists only these, and tokens only get these.
- Confidential or public - A confidential application, which has a server, gets a client secret, shown once. A public one, such as a desktop or browser application, has no secret and must use PKCE. Regenerate a confidential application’s secret from its actions.
- First party - Skips the consent screen, for applications your organization runs and trusts.
Revoking an application removes everyone’s access to it and every connection.
The Flow
Section titled “The Flow”- The application sends the person to
GET /oauth/authorizewithclient_id,redirect_uri,response_type=code, thescopeit wants, astate, and a PKCEcode_challenge(S256). - A signed-out person signs in first, then returns to the consent screen.
- The consent screen shows the application, what it will be able to do, where the person returns (with the punycode form of an internationalized domain, which can imitate another site’s name), and who is signed in, with a Switch Account link. The person approves or denies; denying returns to the application with
error=access_denied. - The application exchanges the code at
POST /oauth/token(grant_type=authorization_code, with itscode_verifier) for an access token that lasts one hour and a refresh token. - It refreshes at
POST /oauth/token(grant_type=refresh_token). Each refresh token can be used once and lasts 30 days, so a connection unused for 30 days expires.
The consent screen offers, and the token carries, only the requested scopes the person holds themselves, so an application can never do more than the person who connected it. The consent screen asks again when an application requests scopes the person hasn’t granted, or after its access tokens have expired. An administrator impersonating someone can’t approve applications for them. A person sent to the consent screen by an application that was deleted or revoked sees an Application Not Registered page instead of Passport’s JSON error. No other site can show the consent screen in a frame, so none can trick someone into pressing Approve.
Who may approve, which scopes a connection gets and what the screen shows are decided in App\Domains\Api\Passport\OAuthConsent. Change the screen’s wording in resources/views/public/oauth/authorize.blade.php, and what it decides in OAuthConsent.
Switching account signs out of this application and returns to sign-in. With single sign-on, the identity provider may still be signed in and sign the same person straight back in; they may need to sign out of Northwestern first.
Connections
Section titled “Connections”Approving creates a connection between the person and the application, which the person sees on their Account area’s Connected Applications page and can disconnect. Disconnecting revokes the access tokens, refresh tokens and authorization codes the application holds for that person; other people connected to the same application are unaffected. Administrators disconnect an application for a person on the person’s Connected Applications tab, recorded in the person’s audit history.
The first time an application connects, the person is told in the notification bell, and by email unless they turned that off in their preferences.
Signing Keys
Section titled “Signing Keys”Passport signs access tokens with an RSA key pair: PASSPORT_PRIVATE_KEY and PASSPORT_PUBLIC_KEY in deployed environments, key files in storage/ locally. Setting them up, what rotating them or APP_KEY invalidates, the cleanup tasks and the refusals that protect credentials are in API Operations.
Local Development
Section titled “Local Development”In the local environment, db:rebuild seeds a demo API user, api-nuit, with a service client whose ID and secret are fixed and printed at the end of the rebuild, so scripts and agents can call the API across rebuilds. In every other environment the demo client’s secret is random and never shown; rotate it in the administration panel to get one.
Secret Expiration Notifications
Section titled “Secret Expiration Notifications”The system emails the API user’s contact address when a client secret approaches its expiration date. A client stops working when its secret expires.
By default, notifications are sent 30, 14, 7, 3 and 1 day before expiration. None is sent on the day the secret expires.
You can adjust these intervals in the configuration:
'client_secret_expiration_notifications' => [ 'enabled' => env('API_CLIENT_SECRET_EXPIRATION_NOTIFICATIONS_ENABLED', true), 'intervals' => [30, 14, 7, 3, 1],],To disable them:
API_CLIENT_SECRET_EXPIRATION_NOTIFICATIONS_ENABLED=falsePersonal access token reminders have their own setting, api.personal_access_tokens.expiration_notifications, turned off with API_PERSONAL_ACCESS_TOKEN_EXPIRATION_NOTIFICATIONS_ENABLED=false.
Request Logging
Section titled “Request Logging”Requests to protected API endpoints are logged to the api_request_logs table for monitoring API usage and debugging issues.
API_REQUEST_LOGGING_ENABLEDtrueTurns request logging on or off
API_REQUEST_LOGGING_SLOW_THRESHOLD_MS500Requests slower than this many milliseconds are marked slow in the administration panel
API_REQUEST_LOG_RETENTION_DAYS90Days before a log entry is deleted by the daily model:prune. See Data
Retention.
What Gets Logged
Section titled “What Gets Logged”The authenticated API routes and the MCP server are logged. Passport’s /oauth/* endpoints, the public routes such as /api/health, and the MCP discovery and registration routes aren’t.
Every log entry includes:
trace_idUnique request identifier, also returned in the X-Trace-Id response
header
user_idID of the user the request acted as: a person, or a service client’s API user
principal_typeuser for a person’s token, client for a service client’s
oauth_client_idID of the OAuth client the access token was issued to
token_idgrant_typeHow the access token was obtained, such as client_credentials
methodpathroute_nameNamed route for the request (if applicable)
ip_addressstatus_codeduration_msrequest_bytesSize of the request body in bytes (if measurable)
response_bytesSize of the response body in bytes (if measurable)
user_agentUser agent string from the request (if provided)
failure_reasonWhy the request was refused or failed, if it was: missing-credentials,
invalid-header-format, token-invalid-or-expired, ip-denied,
unauthorized, validation-failed, conflict, client-error,
database-error or server-error
created_atMCP requests also record their JSON-RPC method, tool and outcome; see MCP Server. Secrets and tokens are never logged.
Sampling Configuration
Section titled “Sampling Configuration”Sampling is off by default (API_REQUEST_LOGGING_SAMPLING_ENABLED=false, API_REQUEST_LOGGING_SAMPLE_RATE=1.0), so every request is logged. To reduce database load, turn on probabilistic sampling:
API_REQUEST_LOGGING_SAMPLING_ENABLED=trueAPI_REQUEST_LOGGING_SAMPLE_RATE=0.1 // Log 10% of requestsProduction Considerations
Section titled “Production Considerations”This is a basic utility, not a replacement for full-scale observability solutions.
Database-based logging helps during local development to spot slow endpoints, and works well for low-traffic non-production environments.
For production applications expecting high volume:
- Use a dedicated APM: Tools like New Relic or Datadog provide deeper insights and avoid database bloat.
- Adjust sampling: If you keep this feature enabled in production, consider setting a very low sampling rate (or disabling it entirely) to avoid the database write overhead.
Rate Limiting
Section titled “Rate Limiting”Two limits apply. A client over either gets a 429 Too Many Requests Problem Details response with a Retry-After header.
- Per IP address, before authentication. The
apilimiter, applied to every API route bythrottleApi()inbootstrap/app.phpand to Passport’s/oauth/*endpoints throughpassport.middleware, guards against floods and token guessing. - Per client, after authentication.
LimitAuthenticatedApiRequestsruns afterAuthenticatePassportToken, so it counts requests per service client (and per user for a person’s token). Integrations behind one gateway IP don’t share a limit.
All rate limits are configured in config/rate-limiting.php.
Browser Access (CORS)
Section titled “Browser Access (CORS)”Integrations call the API from servers, where CORS doesn’t apply, so no browser origin may call it by default. To let a browser application call the API with a bearer token, list its origins in CORS_ALLOWED_ORIGINS (comma-separated). Credentials such as cookies are never allowed. Browser code can read these response headers: X-Trace-Id, Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining, WWW-Authenticate and Mcp-Session-Id. See config/cors.php. The MCP server and the OAuth endpoints its clients call accept any origin, through cors.open_paths.
Authorization
Section titled “Authorization”To handle granular access control for API users, the system leverages the existing role and permission structure.
API Integration Role Type
Section titled “API Integration Role Type”API users can ONLY be assigned roles with a role type of API Integration. This prevents API users from receiving permissions that are not relevant to programmatic access.
API-Relevant Permissions
Section titled “API-Relevant Permissions”When creating/editing API Integration roles, only permissions marked as API-relevant are presented for assignment. These are determined by the isApiRelevant() method on the SystemPermission:
public function isApiRelevant(): bool{ return match ($this) { self::ViewUsers => true, default => false, };}Each API-relevant permission is also an OAuth scope of the same name (view-users), registered with Passport in OAuthServiceProvider. As you add new permissions to the system, ensure to update this method accordingly.
Managing API Access
Section titled “Managing API Access”Two permissions govern the API area of the administration panel:
- Manage API Access (
ManageApiAccess) - manage API users and their service clients, and see API users withoutViewUsers; register, edit and revoke applications, including first-party ones that skip consent; revoke MCP clients; and revoke anyone’s personal access tokens and connected applications - View API Request Logs (
ViewApiRequestLogs) - view request logs and usage charts