Mobile sign-in: OAuth, PKCE and session renewal

Sign users in through the system browser, protect the return to the app and handle expired tokens without repeated or concurrent login attempts.

A mobile phone beside a laptop keyboard

A mobile application cannot keep a shared secret

A native application is distributed to users' devices. A secret embedded in its package cannot reliably establish client identity. In an illustrative citizen app, putting a client_secret in configuration and sending it with every login is therefore insufficient. An OAuth client for a mobile app must be registered and designed as a public client.

IETF RFC 8252 recommends an external user-agent for native apps, typically the system browser's authentication interface. The application need not collect the user's password in its own form or read the sign-in page. Adapt the flow to the identity provider and use a maintained library. Implementing the protocol yourself adds failure points without improving the user experience.

PKCE binds the code to the start of sign-in

For the authorization code flow, the app creates a random code_verifier for this attempt and derives an S256 code_challenge. It sends the challenge in the authorisation request and the verifier when exchanging the code. The server can verify that the exchange is linked to the initiating attempt. An intercepted code alone is insufficient without the correct verifier.

This does not replace redirect URI registration and validation or protection against context substitution. On return, the app validates the expected attempt using its library and provider rules, including state and applicable OpenID Connect nonce checks. It removes temporary attempt data after completion. It cannot accept any callback simply because that callback opens the app's URL scheme.

ID and access tokens have different recipients

OpenID Connect supplies user identity and an ID token for the client. An access token grants access to a protected API. A backend should not automatically accept an ID token intended for the mobile client as authorisation for its own operations. It validates the access token according to the authorisation server's contract: format, validity, recipient, issuer and required permissions.

For a JWT, validation includes signature verification and permitted algorithms, rather than merely decoding the payload. Opaque tokens require the provider's supported verification mechanism. A user identifier in the response does not replace organisation membership checks. A signed-in citizen still needs permission for the particular record, such as their own submission.

Choose storage for the platform

On iOS, Keychain stores sensitive items, with accessibility settings determining when an item is available. Android Keystore protects cryptographic keys; it does not directly store arbitrary token strings. A design can use such a key to protect a stored secret. Choose storage, backup behaviour and availability after locking according to the library and application's needs.

Do not put tokens in analytics events, crash reports or logged URLs. Also decide what happens after device replacement or local-storage failure. If credentials cannot be recovered, the app must enter a controlled signed-out state. It should not keep showing a signed-in screen while endlessly repeating rejected requests.

Coordinate token renewal in one place

After an app returns from the background, four API requests may fail at once with an expired token. If each starts its own refresh, token rotation can cause a second attempt to use an invalidated refresh token. In this illustrative design, one coordinator renews the session. Other requests wait for its result and then use the new access token.

RFC 9700 requires public-client refresh tokens to be protected through sender constraint or rotation. Use the provider's supported mechanism and correctly store the renewal result. Revoked credentials require fresh sign-in. After a network failure, follow the provider's documented retry behaviour; blindly repeating a request may consume an already used token.

Signing out has local and server responsibilities

Local sign-out removes credentials and sensitive local state under the application's policy. Server revocation and provider logout are separate operations depending on the provider's capabilities. An existing access token may remain valid until expiry unless the backend has another revocation mechanism. The product must define signing out this device separately from ending every session.

  • An abandoned or unrelated callback cannot create a user session.
  • Several expired requests cause one coordinated renewal.
  • A revoked refresh token leads to a clear request to sign in again.
  • Sensitive local screens are no longer accessible after sign-out.

Sources and documentation

For implementation, consult the documentation for the version you use.

Put the topic into practice.

Have a process
that needs to change?

Let’s start with how you work today. We’ll choose the technology around it.

Discuss your project