Skip to main content

Passwordless sign-in

Use passwordless sign-in when a trusted partner application already knows the NG-SOS user and needs to open the NG-SOS Portal as that user without asking for an NG-SOS password or showing an interactive identity-provider login.

The flow has a server-to-server part and a browser part:

  1. Your backend requests an access token with the identity.user.signin scope.
  2. Your backend exchanges that access token and the Portal user's name for a short-lived sign-in token.
  3. Your application redirects the user's browser to the Identity Server with that sign-in token.

The result is a normal, non-persistent Identity Server session in the browser. The sign-in token is not an API access token and must never be exposed in application logs or sent to an untrusted client.

If the user should sign in interactively and you only need to choose the agency before login, use Preselect an agency before sign-in instead.

Prerequisites

Obtain these values from NG-SOS during onboarding:

ValuePurpose
client_idIdentifies your partner application.
client_secretAuthenticates your backend. Keep it on the server.
AGENCY_IDUUID of the agency containing the Portal user. The client must be allowed to use this agency.
USERNAMEUser name entered on the Portal login screen.
Portal URLTrusted Portal destination to open after sign-in.

The client must be configured for the identity.user.signin scope, and the user must be assigned to the client and allowed to sign in. Do not implement this flow with client credentials in browser or mobile code: the client secret belongs only on a trusted backend.

1. Request an application access token

Request a client-credentials token from the Identity Server. Send the request as application/x-www-form-urlencoded, not JSON.

curl --request POST "https://identity.ng-sos.com/connect/token" \
--header "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id=YOUR_CLIENT_ID" \
--data-urlencode "client_secret=YOUR_CLIENT_SECRET" \
--data-urlencode "scope=identity.user.signin"

The response contains an OAuth access token:

{
"access_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6...",
"expires_in": 3600,
"token_type": "Bearer",
"scope": "identity.user.signin"
}

Cache the access token until shortly before expires_in; do not request a new application token for every Portal user.

2. Generate a sign-in token

Call POST /Account/GetUserSigninToken from your backend with the access token from step 1:

curl --request POST "https://identity.ng-sos.com/Account/GetUserSigninToken" \
--header "Authorization: Bearer YOUR_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"agencyContext": "AGENCY_ID",
"username": "USERNAME"
}'

The agencyContext in the JSON request identifies the agency containing the user. The agency must match an agency allowed for the client. username is the user name entered on the Portal login screen.

Successful response:

{
"expiresAtUtc": "2030-01-01T12:15:00Z",
"token": "Qm9yZWslMjBkaXZlciUyMHNpZ24taW4lMjB0b2tlbg%3D%3D"
}

The Identity Server URL-encodes the opaque token before returning it, so it is safe to place directly in the token query parameter. It is valid for 15 minutes. Keep it on the server until the browser redirect is created. Do not decode it and do not URL-encode it again.

3. Redirect the browser to the Portal

Send the user's browser to:

https://identity.ng-sos.com/Account/LoginUserByToken?token={TOKEN}&returnUrl={URL_ENCODED_RETURN_URL}

Replace {TOKEN} with the token value from step 2 exactly as returned. Encode the complete returnUrl value once. For example:

https://identity.ng-sos.com/Account/LoginUserByToken?token=Qm9yZWslMjBkaXZlciUyMHNpZ24taW4lMjB0b2tlbg%3D%3D&returnUrl=https%3A%2F%2Fportal.ng-sos.com%2Fincident-list

In a web application, construct the redirect only on the trusted backend and return it to the browser as an HTTP redirect:

HTTP/1.1 302 Found
Location: https://identity.ng-sos.com/Account/LoginUserByToken?token={TOKEN}&returnUrl=https%3A%2F%2Fportal.ng-sos.com%2Fincident-list

The backend must insert {TOKEN} exactly as returned by the API. Do not use a URL builder that encodes the token value a second time: the API has already URL-encoded it, so encoding it again changes %3D into %253D and invalidates the token. Encode the complete returnUrl value once.

Open the redirect URL in the user's browser so the Identity cookie is stored in that browser.

What happens at the redirect

GET /Account/LoginUserByToken is anonymous and returns an HTTP 302 Found redirect.

  • If the token is valid, unexpired and belongs to a user who can sign in, the Identity Server creates a non-persistent authentication cookie and redirects to returnUrl.
  • If the token is invalid, expired, unknown or belongs to a user who cannot sign in, the browser is still redirected, but no new session is created.
  • If the browser already has an authenticated Identity Server session, that session is left unchanged and the browser is redirected without applying the token to another user.
  • A missing or rejected returnUrl falls back to /.
  • Absolute returnUrl values are accepted only for configured NG-SOS application origins. Local paths such as /incident-list are also accepted; arbitrary external URLs and non-HTTP schemes are rejected.

Treat the token as a short-lived bearer credential: use HTTPS, do not log the URL, and do not put it in analytics or referrer data.

Common errors from GetUserSigninToken

HTTP statusCodeMeaning
400ValidationFailedA supplied agency ID is empty or invalid, or username is missing.
400UsernameAmbiguousThe user name identifies more than one user allowed for the client.
401AuthenticationRequiredThe bearer access token is missing or invalid.
403ClientNotAllowedThe client is not allowed to issue a token for the selected agency or user.
403AuthorizationDeniedThe access token does not grant this operation.
404UserNotFoundThe requested user does not exist.
415UnsupportedMediaTypeThe request body is not JSON.
500InternalServerErrorThe request could not be completed.

For OAuth token errors such as invalid_client or invalid_scope, see Request an application token and the Identity API Reference.

Complete example

PowerShell

$identity     = 'https://identity.ng-sos.com'
$clientId = 'YOUR_CLIENT_ID'
$clientSecret = 'YOUR_CLIENT_SECRET'
$agencyId = 'YOUR_AGENCY_ID'
$portalUser = 'YOUR_USERNAME'
$returnUrl = 'https://portal.ng-sos.com/incident-list'

$token = Invoke-RestMethod -Method Post -Uri "$identity/connect/token" -Body @{
grant_type = 'client_credentials'
client_id = $clientId
client_secret = $clientSecret
scope = 'identity.user.signin'
}

$signin = Invoke-RestMethod -Method Post -Uri "$identity/Account/GetUserSigninToken" `
-Headers @{ Authorization = "Bearer $($token.access_token)" } `
-ContentType 'application/json' `
-Body (@{ agencyContext = $agencyId; username = $portalUser } | ConvertTo-Json)

$loginUrl = "$identity/Account/LoginUserByToken" +
"?token=$($signin.token)" +
"&returnUrl=$([uri]::EscapeDataString($returnUrl))"

Start-Process $loginUrl

Bash

#!/bin/bash
identity="https://identity.ng-sos.com"
client_id="YOUR_CLIENT_ID"
client_secret="YOUR_CLIENT_SECRET"
agency_id="YOUR_AGENCY_ID"
portal_user="YOUR_USERNAME"
return_url="https://portal.ng-sos.com/incident-list"

json_value() {
sed -n "s/.*\"$1\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p"
}

access_token=$(curl -s --request POST "$identity/connect/token" \
--header "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id=$client_id" \
--data-urlencode "client_secret=$client_secret" \
--data-urlencode "scope=identity.user.signin" | json_value access_token)

signin_token=$(curl -s --request POST "$identity/Account/GetUserSigninToken" \
--header "Authorization: Bearer $access_token" \
--header "Content-Type: application/json" \
--data "{\"agencyContext\":\"$agency_id\",\"username\":\"$portal_user\"}" | json_value token)

return_url_encoded=$(printf '%s' "$return_url" | sed -e 's/%/%25/g' -e 's/ /%20/g' \
-e 's/#/%23/g' -e 's/&/%26/g' -e 's/+/%2B/g' -e 's|/|%2F|g' \
-e 's/:/%3A/g' -e 's/=/%3D/g' -e 's/?/%3F/g')

login_url="$identity/Account/LoginUserByToken?token=$signin_token&returnUrl=$return_url_encoded"

open "$login_url" # macOS; on Linux use xdg-open

References