Identity
On this page
Mock identity providers
A mock identity provider (IdP) is a fake OIDC and SAML 2.0 provider for testing your app's single sign-on: no Okta tenant, no shared test accounts, and any user, group or claim you need. Create one under Identity providers. Each IdP starts with two sample users.
Mock IdPs are for testing. Anyone who can reach an IdP's login screen can sign in as any of its users (unless you protect it). Never use one to guard real data.
Endpoints
Each IdP has its own issuer, https://testlie.com/idp/<slug>. The Setup tab shows the values for your IdP.
| Your app needs | Value |
|---|---|
| OIDC issuer | https://testlie.com/idp/<slug> |
| Discovery | https://testlie.com/idp/<slug>/.well-known/openid-configuration |
| Client ID and secret | On the Setup tab (rotate the secret with POST /api/idps/:id/rotate, { "what": "secret" }) |
| Redirect URI | Register your app's callback under Redirect URIs (exact match; custom schemes for mobile apps work) |
| SAML metadata | https://testlie.com/idp/<slug>/saml/metadata (entity ID, SSO URL for HTTP-Redirect and HTTP-POST, signing certificate) |
| SAML SSO URL | https://testlie.com/idp/<slug>/saml/sso |
| SAML certificate | https://testlie.com/idp/<slug>/saml/certificate |
| SAML ACS URL and SP entity ID | Set them on the Setup tab, or leave empty to take them from each AuthnRequest |
OIDC: authorization code flow with PKCE (S256 or plain; required for public clients), refresh tokens with the offline_access scope (rotated on every use), client credentials, userinfo, JWKS and RP-initiated logout (end_session_endpoint). Response modes query, fragment and form_post. Clients authenticate with client_secret_basic or client_secret_post, or as public clients (none). Tokens are RS256 JWTs; codes are single-use and expire after two minutes; token lifetime is configurable (default one hour). Scopes: openid profile email groups offline_access.
SAML: SP-initiated (HTTP-Redirect and HTTP-POST) and IdP-initiated sign-in. Responses and assertions are signed (RSA-SHA256). NameID format defaults to email address; persistent and unspecified are available.
Test users
Users have an email, name, groups and any extra JSON claims. Everything appears in the ID token, the userinfo response and the SAML attributes: email, name, given_name, family_name, groups, and every custom claim. The sub claim (and the persistent NameID) is the user's subject, which you can set to match existing accounts in your app.
curl -X POST https://testlie.com/api/idps/<idp id>/users -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
-d '{ "email": "admin@acme.test", "name": "Ada Admin", "groups": ["admins"], "claims": { "tenant": "acme", "roles": ["owner"] } }'Signing in
When your app redirects to the IdP, a login screen lists the test users. Pick one to sign in, or Deny to send back access_denied (OIDC) or a RequestDenied status (SAML), so you can test your error handling too.
The Activity tab lists every authorize, token, userinfo, logout and SAML request with its details, including the full SAML request and response XML. Test OIDC and Test SAML run a sign-in against the built-in debugger, which redeems the code or checks the assertion like a real app and shows the decoded result.
Automated tests: auto-login
Set an auto-login user on the IdP (Setup tab, or PATCH /api/idps/:id with { "autoLoginUserId": "<user id>" }). The IdP then signs straight in as that user with no login screen, and prompt=none succeeds, so a Playwright or Cypress test can click "Sign in with SSO" and land signed in.
Protected IdPs
On paid plans you can protect an IdP with PATCH /api/idps/:id and { "requireTeamSession": true }. The login screen then only works for signed-in members of your team; anyone else is sent to sign in first. That keeps an IdP's users private even though its URL is public.
Browsers in CI are not signed in to Testlie, so give them the access cookie instead: set tl_idp_access to the IdP's client secret on the Testlie domain before starting the sign-in.
// Playwright
await context.addCookies([{ name: "tl_idp_access", value: process.env.IDP_CLIENT_SECRET!, url: "https://testlie.com" }]);// Cypress
cy.setCookie("tl_idp_access", Cypress.env("IDP_CLIENT_SECRET"), { domain: new URL("https://testlie.com").hostname });The cookie works together with auto-login: a protected IdP checks access first, then signs in as the auto-login user.
Setup recipes
Replace <slug>, the client ID and secret with the values from the Setup tab, and register the callback URL shown in each recipe as a redirect URI on the IdP.
Auth.js / NextAuth (OIDC)
// auth.ts (Auth.js v5). Redirect URI: http://localhost:3000/api/auth/callback/testlie
import NextAuth from "next-auth";
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
{
id: "testlie",
name: "Mock SSO",
type: "oidc",
issuer: "https://testlie.com/idp/<slug>",
clientId: process.env.MOCK_IDP_CLIENT_ID,
clientSecret: process.env.MOCK_IDP_CLIENT_SECRET,
authorization: { params: { scope: "openid profile email groups" } },
profile: (p) => ({ id: p.sub, name: p.name, email: p.email, groups: p.groups }),
},
],
});Spring Security (OIDC)
# application-test.properties. Redirect URI: http://localhost:8080/login/oauth2/code/testlie
spring.security.oauth2.client.registration.testlie.client-id=${MOCK_IDP_CLIENT_ID}
spring.security.oauth2.client.registration.testlie.client-secret=${MOCK_IDP_CLIENT_SECRET}
spring.security.oauth2.client.registration.testlie.scope=openid,profile,email,groups
spring.security.oauth2.client.provider.testlie.issuer-uri=https://testlie.com/idp/<slug>With http.oauth2Login(Customizer.withDefaults()), users land with an OidcUser whose getClaim("groups") holds the test user's groups.
Spring Security (SAML)
# application-test.yml. ACS URL: http://localhost:8080/login/saml2/sso/testlie
spring:
security:
saml2:
relyingparty:
registration:
testlie:
assertingparty:
metadata-uri: https://testlie.com/idp/<slug>/saml/metadataSet the IdP's SP entity ID to Spring's default, http://localhost:8080/saml2/service-provider-metadata/testlie, or leave it empty to accept the issuer from each request.
passport-saml / node-saml (SAML)
// npm install @node-saml/passport-saml. ACS URL: http://localhost:3000/login/callback
import { Strategy as SamlStrategy } from "@node-saml/passport-saml";
passport.use(new SamlStrategy(
{
entryPoint: "https://testlie.com/idp/<slug>/saml/sso",
issuer: "my-app", // the SP entity ID
callbackUrl: "http://localhost:3000/login/callback",
idpCert: process.env.MOCK_IDP_CERT, // from …/saml/certificate (PEM body)
wantAssertionsSigned: true,
},
(profile, done) => done(null, { email: profile.email, groups: profile.groups }),
(profile, done) => done(null, profile),
));Django allauth (OIDC)
# settings.py. Redirect URI: http://localhost:8000/accounts/oidc/testlie/login/callback/
INSTALLED_APPS += ["allauth", "allauth.account", "allauth.socialaccount", "allauth.socialaccount.providers.openid_connect"]
SOCIALACCOUNT_PROVIDERS = {
"openid_connect": {
"APPS": [
{
"provider_id": "testlie",
"name": "Mock SSO",
"client_id": os.environ["MOCK_IDP_CLIENT_ID"],
"secret": os.environ["MOCK_IDP_CLIENT_SECRET"],
"settings": {"server_url": "https://testlie.com/idp/<slug>"},
}
]
}
}allauth's SAML provider (allauth.socialaccount.providers.saml) works too: point its idp.metadata_url at the SAML metadata URL.
Rails (OmniAuth)
# Gemfile: gem "omniauth_openid_connect"
# config/initializers/omniauth.rb. Redirect URI: http://localhost:3000/auth/testlie/callback
Rails.application.config.middleware.use OmniAuth::Builder do
provider :openid_connect,
name: :testlie,
issuer: "https://testlie.com/idp/<slug>",
discovery: true,
scope: %i[openid profile email groups],
response_type: :code,
pkce: true,
client_options: {
identifier: ENV["MOCK_IDP_CLIENT_ID"],
secret: ENV["MOCK_IDP_CLIENT_SECRET"],
redirect_uri: "http://localhost:3000/auth/testlie/callback"
}
endFor SAML use omniauth-saml with idp_sso_service_url: "https://testlie.com/idp/<slug>/saml/sso" and the certificate from …/saml/certificate (or OneLogin::RubySaml::IdpMetadataParser on the metadata URL).