Skip to main content

Integrate the User Service Without the React Library

Introduction

Most of the User Service guides show how to integrate authentication using the @axinom/mosaic-user-auth React library. The library is the recommended path for web and React Native applications because it takes care of the token caching, cookie handling, and renewal timing for you.

There are cases where the React library is not an option, for example a native mobile app on iOS or Android, a game console, a server-side backend, or any client built on a stack the library does not support. This article describes the underlying HTTP and GraphQL contract that the library talks to, so you can integrate the same flows from any platform.

Everything below is a plain HTTP interaction. The examples use generic requests (shown with curl and raw GraphQL) instead of any specific language or framework.

What you need before you start

To talk to the User Service you need the identifiers of your application, which an administrator configures in the Admin Portal:

  • Tenant ID - a UUID identifying your tenant.
  • Environment ID - a UUID identifying the environment.
  • Application ID - a UUID identifying the end-user application.

You also need a few base URLs. The User Service exposes two kinds of surfaces:

  • Authentication endpoints (https://user-auth.service.eu.axinom.net) - the OAuth redirect flow, the token endpoint, and the sign-in/sign-out endpoints. These use browser cookies to keep a session.
  • GraphQL APIs (https://user.service.eu.axinom.net) - the End-User API (/graphql) for profile operations, and the Management API (/graphql-management) for application-level operations. An additional AxAuth Management GraphQL API handles sign-up and password reset.

The authentication endpoints and the GraphQL APIs are served from different hosts. Browser applications do not call the authentication host directly: because the session cookie is same-site, they reach the authentication endpoints through a reverse proxy on their own domain (see Set Up an Authentication Proxy). Server-side and native clients, which are not subject to browser cookie rules, may call the authentication host https://user-auth.service.eu.axinom.net directly.

Example hosts used in this guide

The browser-facing examples send authentication requests to https://id.example.com, a placeholder for your own proxy domain. Replace it with your actual proxy DNS. GraphQL examples use the real host https://user.service.eu.axinom.net.

Rather than hard-coding the GraphQL URLs, discover them from the well-known document described next.

Discover the endpoints

Send a GET request to the /.well-known endpoint of the authentication base URL. No authentication is required.

curl https://id.example.com/.well-known

The response lists the endpoints you will use in the rest of this guide:

{
"userServiceManagementGQL": "https://user.service.eu.axinom.net/graphql-management",
"userServiceEndUserGQL": "https://user.service.eu.axinom.net/graphql",
"axAuthManagementGQL": "https://<axauth-service>/graphql",
"axAuthEndpoint": "https://<axauth-service>/"
}
  • userServiceEndUserGQL - the End-User GraphQL API, used with an end-user access token to manage profiles.
  • userServiceManagementGQL - the Management GraphQL API, used for operations such as authenticating an end-user application.
  • axAuthManagementGQL - the AxAuth Management GraphQL API, used for the standalone sign-up and password reset flows.

The axAuth* values point at a separate AxAuth host and can differ per environment, so always read them from this response rather than hard-coding them.

The authentication model

The authentication endpoints are grouped under a base URL you register for your application (referred to below as the auth base URL). When a user signs in, the service stores a long-lived refresh token and returns it to the browser as an HttpOnly cookie named AX_REFRESH_TOKEN. The cookie is scoped to the path /<tenantId>/<environmentId>/<applicationId>, so the same browser can hold separate sessions for several applications.

You never read the refresh cookie directly. Instead, you call the token endpoint, which reads the cookie and returns a short-lived access token. That access token is the credential you attach to all Mosaic end-user API calls.

Two rules apply to the browser requests:

  • Send requests to these endpoints with credentials (cookies) included, so the AX_REFRESH_TOKEN cookie travels with them.
  • The auth base URL and the URLs your application redirects to must be registered for the application (as allowed proxy URLs and allowed origins). Requests from unregistered URLs are rejected.

Sign in with an external identity provider

This is the redirect based flow used for Google, Apple, Facebook, and any custom OAuth 2.0 / OpenID Connect provider.

1. List the configured identity providers

curl "https://id.example.com/<tenantId>/<environmentId>/<applicationId>/get-user-auth-idp-config"
{
"code": "SUCCESS",
"availableIdentityProviders": [
{
"idpConnectionId": "1d0b...",
"providerId": "AX_GOOGLE",
"title": "Google",
"providerIconUrl": "https://...",
"sortOrder": 1,
"clientId": "..."
}
]
}

Render a sign-in button per provider. The code can also be NO_ACTIVE_IDPS when no providers are configured for the application.

2. Redirect the user to the authorization endpoint

When the user picks a provider, send the browser to the /oauth endpoint with the following query parameters:

ParameterDescription
tenantIdYour tenant ID.
environmentIdYour environment ID.
applicationIdYour application ID.
idpConnectionIdThe idpConnectionId of the chosen provider from step 1.
originUrlWhere to send the user after sign-in completes. Must be a registered origin for the app.
userAuthProxyUrlYour proxy's base URL, on your own domain (this guide uses https://id.example.com). Must be registered as an allowed proxy URL for the app.
encryptionKeyNative apps only (see Native applications). Omit for web apps.
https://id.example.com/oauth?tenantId=<tenantId>&environmentId=<environmentId>&applicationId=<applicationId>&idpConnectionId=<idpConnectionId>&originUrl=<originUrl>&userAuthProxyUrl=https://id.example.com

The service redirects the browser to the identity provider, using the OAuth 2.0 Authorization Code flow with PKCE.

3. The callback completes the sign-in

After the user authenticates with the provider, the provider redirects back to the service's callback endpoint. The service exchanges the authorization code for tokens, creates or updates the user record, sets the AX_REFRESH_TOKEN cookie, and finally redirects the browser to the originUrl you supplied. At that point the user has a session and you can request an access token.

Sign in with email and password

For applications configured with an AxAuth IDP (the standalone user store), users can sign in with a username and password directly, without a redirect.

POST to the sign-in-with-credentials endpoint. The connectionId is the idpConnectionId of the AxAuth provider, which you obtain the same way as any other provider from the well-known listing. Send the request with credentials included so the response cookie is stored.

curl -X POST "https://id.example.com/<tenantId>/<environmentId>/<applicationId>/sign-in-with-credentials" \
-H "Content-Type: application/json" \
--cookie-jar cookies.txt \
-d '{
"connectionId": "<axAuthIdpConnectionId>",
"email": "user@example.com",
"password": "the-password"
}'
{ "code": "SUCCESS" }

On success the AX_REFRESH_TOKEN cookie is set, exactly as in the redirect flow. Then retrieve the access token from the token endpoint (next section). Possible non-success codes include INVALID_CREDENTIALS, INVALID_IDP_CONNECTION, and APPLICATION_NOT_ACTIVE.

Get and renew the access token

Call the token endpoint to obtain an access token. This is the same call whether the user signed in through an external provider or with credentials.

The request must carry the AX_REFRESH_TOKEN cookie that was set during sign-in; that cookie is what identifies the session. In a browser it is sent automatically, provided the request goes to your proxy domain with credentials enabled. A native client must attach the cookie it stored from the sign-in handoff (see Native applications).

curl "https://id.example.com/<tenantId>/<environmentId>/<applicationId>/token" \
--cookie cookies.txt
{
"code": "SUCCESS",
"tenantId": "...",
"environmentId": "...",
"applicationId": "...",
"user": {
"id": "...",
"name": "Jane Doe",
"email": "user@example.com",
"profileId": "...",
"token": {
"accessToken": "eyJ...",
"expiresInSeconds": 3600,
"expiresAt": "2026-07-13T12:00:00.000Z"
}
},
"extensions": {}
}

Use user.token.accessToken as a Bearer token for Mosaic end-user API calls.

The access token is short-lived. To renew it, simply call the token endpoint again before expiresAt; as long as the refresh cookie is still valid, a fresh access token is returned. If the refresh cookie is missing or expired, the response code is NEEDS_LOGIN and the user must sign in again. Other non-success codes include ACCOUNT_NOT_ACTIVE and USER_NOT_FOUND.

tip

Cache a valid access token and reuse it until shortly before it expires, then request a new one. Requesting a token on every API call is unnecessary and slower.

Sign out

Call the sign-out endpoint with the cookie. It invalidates the session and clears the AX_REFRESH_TOKEN cookie.

Sign-out is only needed when the user explicitly chooses to stop being signed in (for example, a "Log out" action). You do not need to call it to end a session when the user simply closes the app or the browser: the access token expires on its own, and the session stays valid until it expires (the refresh session lifetime) or the user signs out. Calling sign-out is what deliberately ends the "stay signed in" state.

curl "https://id.example.com/<tenantId>/<environmentId>/<applicationId>/sign-out" \
--cookie cookies.txt
{ "code": "SUCCESS", "message": "User signed out." }

Native applications

Native apps (mobile, smart TV, consoles, and similar) have two ways to sign a user in. Choose based on the device:

Option 1: Sign in on the device (external IDP or email and password)

Use this when the device can present a sign-in UI: a browser or WebView for an external IDP, or an email and password form for AxAuth. It is the same flow as on the web, with one difference: a native app usually cannot rely on the browser to store the HttpOnly session cookie, so the sign-in returns the cookie in an encrypted form for the app to store and replay.

When starting the redirect flow, add an encryptionKey query parameter to the /oauth request: a 32-byte key encoded as a hex string. After a successful sign-in, instead of setting a cookie the service appends a COOKIE_OPTIONS query parameter to the originUrl redirect. That value is the encrypted cookie.

The app decrypts COOKIE_OPTIONS using the same key by calling the decryptWithKeyAes mutation on the Management GraphQL API, then stores the resulting cookie and attaches it to subsequent token and sign-out requests as the AX_REFRESH_TOKEN cookie.

The application must be configured as a Native application, and the originUrl must match one of its registered origins.

Option 2: Device Authorization flow (keyboard-less or shared devices)

Use this for devices where typing credentials is awkward or impossible, such as smart TVs, consoles, and streaming sticks. The device shows a short code that the user approves from a phone or laptop; the device never handles credentials or cookies itself. This flow has its own guide: Sign In on Smart TVs and Other Devices.

Sign up and reset passwords (AxAuth)

Standalone sign-up and password reset are handled by the AxAuth Management GraphQL API (axAuthManagementGQL from the well-known document). These mutations do not require an access token. The oAuthClientId in the inputs is the clientId of the AxAuth provider from the provider listing.

Both flows are two-step and rely on a one-time password (OTP). The service sends the OTP to a webhook you configure on the AxAuth user store, and your backend forwards it to the user (typically by email). See Sign in with AxAuth IDP for the configuration details.

Initiate sign-up

mutation InitiateEndUserSignUp($input: InitiateEndUserSignUpInput!) {
initiateEndUserSignUp(input: $input) {
idpUserId
}
}
{
"input": {
"oAuthClientId": "<axAuthClientId>",
"email": "user@example.com",
"password": "the-password",
"firstName": "Jane",
"lastName": "Doe"
}
}

The password can be supplied here or deferred to the complete step, but it must be provided in exactly one of the two.

Complete sign-up

mutation CompleteEndUserSignUp($input: CompleteEndUserSignUpInput!) {
completeEndUserSignUp(input: $input) {
idpUserId
}
}
{ "input": { "signUpOtp": "123456", "password": "the-password" } }

Only verified users can sign in, so the sign-up must be completed before the first credential sign-in.

Reset a password

Initiate the reset, then complete it with the OTP and the new password:

mutation InitiateEndUserPasswordReset($input: InitiateEndUserPasswordResetInput!) {
initiateEndUserPasswordReset(input: $input) {
idpUserId
}
}
{ "input": { "oAuthClientId": "<axAuthClientId>", "email": "user@example.com" } }
mutation CompleteEndUserPasswordReset($input: CompleteEndUserPasswordResetInput!) {
completeEndUserPasswordReset(input: $input) {
idpUserId
}
}
{ "input": { "resetOtp": "123456", "newPassword": "the-new-password" } }

To check whether an OTP is still valid before submitting the full form, use the checkEndUserSignUpOtp and checkEndUserPasswordResetOtp mutations, which return { isOtpValid }.

Manage user profiles

A user can have multiple profiles. Profile operations use the End-User GraphQL API (userServiceEndUserGQL) and require the access token as a Bearer token in the Authorization header.

curl -X POST "https://user.service.eu.axinom.net/graphql" \
-H "Authorization: Bearer <accessToken>" \
-H "Content-Type: application/json" \
-d '{ "query": "query { userProfiles { nodes { id displayName defaultProfile profilePictureUrl } } }" }'

The operations available:

# List all profiles
query GetUserProfiles {
userProfiles {
nodes { id displayName defaultProfile profilePictureUrl }
}
}

# Get one profile
query GetUserProfile($profileId: UUID!) {
userProfile(id: $profileId) { id displayName defaultProfile profilePictureUrl }
}

# Create a profile
mutation CreateUserProfile($input: CreateUserProfileInput!) {
createUserProfile(input: $input) {
userProfile { id displayName defaultProfile profilePictureUrl }
}
}

# Update a profile
mutation UpdateUserProfile($input: UpdateUserProfileInput!) {
updateUserProfile(input: $input) {
userProfile { id displayName defaultProfile profilePictureUrl }
}
}

# Delete a profile
mutation DeleteUserProfile($input: DeleteUserProfileInput!) {
deleteUserProfile(input: $input) {
userProfile { id }
}
}

# Set the active profile
mutation SetActiveProfile($profileId: UUID!) {
setActiveProfile(profileId: $profileId) {
id displayName defaultProfile profilePictureUrl
}
}

Setting the active profile changes which profile the next access token represents. After calling setActiveProfile, request a new access token so the updated profileId is reflected in the token payload.

For the full explanation of what profiles are and how they behave, see Manage User Profiles.

Authenticate an end-user application

Some scenarios need an application token (a token representing the application itself, not a signed-in user). Request one from the Management GraphQL API (userServiceManagementGQL) with the application key configured in the Admin Portal.

mutation AuthenticateEndUserApplication($input: AuthenticateEndUserApplicationInput!) {
authenticateEndUserApplication(input: $input) {
accessToken
expiresInSeconds
tokenType
}
}
{
"input": {
"tenantId": "...",
"environmentId": "...",
"applicationId": "...",
"applicationKey": "..."
}
}

Using the access token

The access token returned by the token endpoint (or a delegated or application token) is a JWT. Attach it as a Bearer token in the Authorization header when calling any Mosaic end-user facing service, such as Personalization or Entitlement.

Authorization: Bearer <accessToken>

See also

Was this page helpful?