본문으로 건너뛰기

Device flow: Auth with Logto

노트:

This guide assumes you have created an Application of type "Native" with device flow as the authorization flow in the Logto Console.

Introduction

The OAuth 2.0 device authorization grant (device flow) is designed for devices with limited input capabilities, such as smart TVs, game consoles, CLI tools, and IoT devices. It allows users to start the sign-in process on the device but complete authentication on a separate device with a browser, like a phone or laptop.

Since the device itself cannot handle a browser-based sign-in flow, the device displays a short code and a URL. The user visits that URL on another device, enters the code, and signs in. Meanwhile, the original device polls Logto until the authorization is complete.

Get application credentials

In your Logto Console, navigate to your application details page to get the following credentials:

  • App ID: The unique identifier of your application (also known as client_id).
  • Logto endpoint: Your Logto authorization server endpoint. You can find it in the Logto Console under "Application details".

For Logto Cloud, the endpoint is https://{your-tenant-id}.logto.app.

노트:

Device flow apps are public clients, so no App Secret is required.

Request a device code

Start the device flow by sending a POST request to the device authorization endpoint:

curl --request POST 'https://your.logto.endpoint/oidc/device/auth' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=your-application-id' \
--data-urlencode 'scope=openid offline_access profile'

The response includes:

FieldDescription
device_codeA unique code for your app to use when polling the token endpoint.
user_codeA short code to display to the user for them to enter in the browser.
verification_uriThe URL where the user enters the user_code.
verification_uri_completeA URL with the user_code pre-filled. Users can visit this URL directly to skip manual code entry — you can present it as a QR code, a clickable link, or any other method.
expires_inThe lifetime in seconds of device_code and user_code. Stop polling after this expires.

Display the verification URL to the user

Show the user_code and verification_uri on your device screen.

Alternatively, you can use verification_uri_complete which has the code pre-filled — the user only needs to confirm. How you present it is up to you: a QR code, a clickable link, etc.

Poll for tokens

While the user completes authentication in the browser, your device should poll the token endpoint. Your app should wait at least 5 seconds between polling requests:

curl --request POST 'https://your.logto.endpoint/oidc/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=your-application-id' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:device_code' \
--data-urlencode 'device_code=DEVICE_CODE'

Replace DEVICE_CODE with the device_code value from the device authorization response.

Stop polling when:

  • You receive a successful token response.
  • The expires_in time from the device code response has elapsed.
  • You receive a non-retryable error such as expired_token or access_denied.

Token response

After the user approves, the response includes:

FieldDescription
access_tokenThe access token. This is an opaque string by default; when a resource is requested, it is a JWT with aud set to the resource URI.
id_tokenThe ID token containing user identity claims. Only present when openid scope is requested.
refresh_tokenUsed to obtain new tokens without re-authentication. Only present when offline_access scope is requested.
token_typeAlways Bearer.
expires_inToken lifetime in seconds.
scopeThe scopes granted by the authorization server.

Checkpoint: Test your device flow

Now, test your device flow integration:

  1. Run your app and trigger the device flow to get a device_code and user_code.
  2. Open the verification_uri in a browser and enter the user_code, or use verification_uri_complete to skip manual code entry.
  3. Complete the sign-in process in the browser.
  4. Verify that your app receives tokens after polling.

Get user information

Decode ID token claims

The id_token returned in the token response is a standard JSON Web Token (JWT). You can decode the Base64URL-encoded payload (the second part of the JWT, split by .) to access basic user claims without an additional network request.

The decoded payload contains claims like sub (user ID), name, email, etc., depending on the scopes requested.

:

For production use, you should validate the JWT signature before trusting its claims. Use the JWKS from your Logto endpoint (https://your.logto.endpoint/oidc/jwks) to verify the token.

Fetch from userinfo endpoint

The ID token contains basic claims based on the requested scopes. Some extended claims (like custom_data, identities) are only available via the OIDC UserInfo endpoint:

curl --request GET 'https://your.logto.endpoint/oidc/me' \
--header 'Authorization: Bearer ACCESS_TOKEN'

Replace ACCESS_TOKEN with the opaque access token (not the JWT resource token) obtained from the token response. The response is a JSON object containing the user's claims based on the granted scopes.

Request additional claims

You may find some user information is missing in the ID token. This is because OAuth 2.0 and OpenID Connect (OIDC) are designed to follow the principle of least privilege (PoLP), and Logto is built on top of these standards.

By default, limited claims are returned. If you need more information, you can request additional scopes to access more claims.

정보:

A "claim" is an assertion made about a subject; a "scope" is a group of claims. In the current case, a claim is a piece of information about the user.

Here's a non-normative example the scope - claim relationship:

:

The "sub" claim means "subject", which is the unique identifier of the user (i.e. user ID).

Logto SDK will always request three scopes: openid, profile, and offline_access.

To request additional scopes, include them in the scope parameter of the device authorization request. For example, to request the user's email and phone:

curl --request POST 'https://your.logto.endpoint/oidc/device/auth' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=your-application-id' \
--data-urlencode 'scope=openid offline_access profile email phone'

Scopes and claims

Here's the list of supported scopes and the corresponding claims:

Standard OIDC scopes

openid (default)

Claim nameTypeDescription
substringThe unique identifier of the user

profile (default)

Claim nameTypeDescription
namestringThe full name of the user
usernamestringThe username of the user
picturestringURL of the End-User's profile picture. This URL MUST refer to an image file (for example, a PNG, JPEG, or GIF image file), rather than to a Web page containing an image. Note that this URL SHOULD specifically reference a profile photo of the End-User suitable for displaying when describing the End-User, rather than an arbitrary photo taken by the End-User.
created_atnumberTime the End-User was created. The time is represented as the number of milliseconds since the Unix epoch (1970-01-01T00:00:00Z).
updated_atnumberTime the End-User's information was last updated. The time is represented as the number of milliseconds since the Unix epoch (1970-01-01T00:00:00Z).

Other standard claims include family_name, given_name, middle_name, nickname, preferred_username, profile, website, gender, birthdate, zoneinfo, and locale will be also included in the profile scope without the need for requesting the userinfo endpoint. A difference compared to the claims above is that these claims will only be returned when their values are not empty, while the claims above will return null if the values are empty.

노트:

Unlike the standard claims, the created_at and updated_at claims are using milliseconds instead of seconds.

email

Claim nameTypeDescription
emailstringThe email address of the user
email_verifiedbooleanWhether the email address has been verified

phone

Claim nameTypeDescription
phone_numberstringThe phone number of the user
phone_number_verifiedbooleanWhether the phone number has been verified

address

Please refer to the OpenID Connect Core 1.0 for the details of the address claim.

정보:

Scopes marked with (default) are always requested by the Logto SDK. Claims under standard OIDC scopes are always included in the ID token when the corresponding scope is requested — they cannot be turned off.

Extended scopes

The following scopes are extended by Logto and will return claims through the userinfo endpoint. These claims can also be configured to be included directly in the ID token through Console > Custom JWT. See Custom ID token for more details.

custom_data

Claim nameTypeDescriptionIncluded in ID token by default
custom_dataobjectThe custom data of the user

identities

Claim nameTypeDescriptionIncluded in ID token by default
identitiesobjectThe linked identities of the user
sso_identitiesarrayThe linked SSO identities of the user

roles

Claim nameTypeDescriptionIncluded in ID token by default
rolesstring[]The roles of the user

urn:logto:scope:organizations

Claim nameTypeDescriptionIncluded in ID token by default
organizationsstring[]The organization IDs the user belongs to
organization_dataobject[]The organization data the user belongs to
노트:

These organization claims can also be retrieved via the userinfo endpoint when using an opaque token. However, opaque tokens cannot be used as organization tokens for accessing organization-specific resources. See Opaque token and organizations for more details.

urn:logto:scope:organization_roles

Claim nameTypeDescriptionIncluded in ID token by default
organization_rolesstring[]The organization roles the user belongs to with the format of <organization_id>:<role_name>

API resources and organizations

We recommend to read 🔐 Role-Based Access Control (RBAC) first to understand the basic concepts of Logto RBAC and how to set up API resources properly.

Request access for API resources

To access a specific API resource, include the resource parameter in the device authorization request:

curl --request POST 'https://your.logto.endpoint/oidc/device/auth' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=your-application-id' \
--data-urlencode 'scope=openid offline_access' \
--data-urlencode 'resource=https://your-api-resource-indicator'

Once the user completes authorization and you receive a refresh token, you can fetch JWT access tokens for the API resource:

curl --request POST 'https://your.logto.endpoint/oidc/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=your-application-id' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token=REFRESH_TOKEN' \
--data-urlencode 'resource=https://your-api-resource-indicator'

The response will contain a JWT access_token with aud set to your API resource indicator.

노트:

The refresh_token is only available when the offline_access scope is included in the initial device authorization request. Always store and use the latest refresh_token, as Logto uses token rotation.

Fetch organization tokens

If organizations is new to you, please read 🏢 Organizations (Multi-tenancy) to get started.

To request organization-related information, add the urn:logto:scope:organizations scope in the device authorization request:

curl --request POST 'https://your.logto.endpoint/oidc/device/auth' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=your-application-id' \
--data-urlencode 'scope=openid offline_access urn:logto:scope:organizations' \
--data-urlencode 'resource=urn:logto:resource:organizations'

Once the user is signed in, you can fetch organization tokens using the refresh token:

curl --request POST 'https://your.logto.endpoint/oidc/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=your-application-id' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token=REFRESH_TOKEN' \
--data-urlencode 'organization_id=your-organization-id'

The response will contain an access token scoped to the specified organization.

Organization API resources

To fetch an access token for an API resource within an organization, include both the resource and organization_id parameters:

curl --request POST 'https://your.logto.endpoint/oidc/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=your-application-id' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token=REFRESH_TOKEN' \
--data-urlencode 'organization_id=your-organization-id' \
--data-urlencode 'resource=https://your-api-resource-indicator'

Further readings

End-user flows: authentication flows, account flows, and organization flows Configure connectors Authorization