Add Lithuanian eID login to your application with OpenID Connect. Your application redirects the person to Tvarka ID, then receives an authorization code and exchanges it for tokens.
All Tvarka APIs · Discovery · Public signing keys
Request access through Tvarka's contact page. Provide the application name, billing company, exact HTTPS redirect URIs, logout redirect URIs, requested signing-key algorithm, login methods and scopes. Choose your client authentication method below. Send only public JWKS for key-based authentication; keep private keys in your own system.
private_key_jwt: your backend authenticates with a signed client assertion and registered public keys.client_secret_basic: your backend authenticates with its issued client ID and secret using HTTP Basic.none: a registered public client uses PKCE without a client secret.Registration is operator-managed. You receive a client ID, the agreed configuration and, where applicable, a one-time client secret. Use these OIDC credentials; Sign, Due Diligence and ATK API keys belong to separate products. Request lt_personal_code only when your written legal basis has been approved. Register test redirect URIs explicitly; localhost HTTP is accepted for registered development callbacks, while ordinary redirects use HTTPS.
| Setting | Value |
|---|---|
| Issuer | https://id.tvarka.pro |
| Discovery | https://id.tvarka.pro/.well-known/openid-configuration |
| Flow | Authorization code; response_type=code |
| PKCE | S256, mandatory for public and confidential clients |
| Request binding | Fresh state and nonce, both required |
| Scopes | openid, optional profile; approved lt_personal_code |
| ID token algorithm | ES256 by default; RS256 when registered |
| Code lifetime | 120 seconds; single use |
| ID/access token lifetime | 300 seconds; access token is opaque |
Use a maintained OIDC client library and the discovered endpoints. Generate new state, nonce and PKCE verifier for every authorization. Persist them with the initiating browser session until the callback is consumed. Keep confidential credentials on your backend.
import base64
import hashlib
import secrets
from urllib.parse import urlencode
def new_login(client_id, redirect_uri):
verifier = secrets.token_urlsafe(48)
challenge = base64.urlsafe_b64encode(
hashlib.sha256(verifier.encode('ascii')).digest()
).decode('ascii').rstrip('=')
pending = {'state': secrets.token_urlsafe(32),
'nonce': secrets.token_urlsafe(32), 'verifier': verifier}
query = {'client_id': client_id, 'redirect_uri': redirect_uri,
'response_type': 'code', 'scope': 'openid profile',
'state': pending['state'], 'nonce': pending['nonce'],
'code_challenge': challenge, 'code_challenge_method': 'S256'}
# Store pending in the initiating browser's server-side session. Do not
# expose the verifier in the authorization URL or in application logs.
return 'https://id.tvarka.pro/authorize?' + urlencode(query), pending
Redirect the browser to the returned URL. The login page offers the methods enabled for your client: Smart-ID, Mobile-ID and/or the Lithuanian ID card. Each authorization requires a fresh eID act; a remembered browser helps select the method but does not silently authenticate. Card-on-phone availability depends on the enabled app integration and a compatible published client.
First compare the returned state with the pending session value using a constant-time comparison, then consume that pending state once. Handle an error response before using code. Send the code to the discovered token endpoint with the same exact redirect URI and original PKCE verifier.
POST /token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <base64(client_id:client_secret)>
grant_type=authorization_code&code=RETURNED_CODE&redirect_uri=REGISTERED_URI&code_verifier=ORIGINAL_VERIFIER
The header above is for client_secret_basic. A public client instead sends client_id in the form without a secret. For private_key_jwt, send client_id, client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer and the assertion. Its iss and sub identify the client; aud is the token endpoint or issuer, exp is short-lived and jti is fresh. Sign with the algorithm/key registered for this client. Never retry a consumed code as a new login; start a fresh authorization after a failed or expired exchange.
Before starting your application session, validate the ID token with the issuer's JWKS and your registered algorithm. Check its signature, exact iss, your client in aud, expiry, issuance time and the original nonce; apply the standard authorized-party checks if multiple audiences are present. Select keys by kid; refresh the trusted issuer's JWKS on an unknown key, then fail if validation still fails. Never accept unsigned tokens or token-supplied key URLs. Follow OIDC ID-token validation.
sub is a stable pairwise identifier within the registered sector. Use issuer plus subject as the identity key; it is not the personal code.profile can add given_name, family_name and birthdate. Missing values are omitted.lt_personal_code is returned only for an approved client and requested scope.amr records smart_id, mobile_id or id_card; NFC card login also includes nfc. auth_time records authentication time. No acr assurance level is currently asserted.Call GET /userinfo with Authorization: Bearer ACCESS_TOKEN for the permitted profile. Verify that its sub matches the ID token. The access token is for UserInfo, not other Tvarka APIs. This flow issues no refresh token: start another authorization when you need fresh authentication.
End your own application session on logout. You can send the access token to POST /revoke with the client's registered authentication method and token_type_hint=access_token. RP-initiated /logout accepts an ID-token hint and a registered post_logout_redirect_uri; an unregistered return target is refused. Tvarka ID has no reusable IdP login session to terminate. Browser recognition and your application's own session are separate.
Successful Smart-ID/Mobile-ID login costs €0.12 + VAT; ID-card login costs €0.08 + VAT. The registered billing entity pays. Failed or cancelled logins are free. Funding is checked before the PIN flow.
invalid_request: check required state/nonce, PKCE S256 and the exact registered redirect URI.invalid_scope: request only scopes approved for this client.invalid_client: check client status, authentication method, secret or registered public keys.invalid_grant: the code expired, was consumed or does not match the verifier/redirect. Restart authorization.login_required with prompt=none: use interactive authorization; every login needs a fresh eID act.access_denied: the person cancelled. Return to your application without creating a session.For support, provide the time, client ID, endpoint and error code. Keep secrets, authorization codes, access/ID tokens and personal identifiers out of support messages.
Production acceptance includes a registered relying party and a real login with each required method. Local protocol tests and public discovery do not establish OpenID Foundation conformance certification. PKCE reference: RFC 7636.