Skip navigation
Documentation

Duo OIDC Auth API - Duo Universal Prompt

Last Updated: September 11th, 2024

The Duo OIDC Auth API is an OIDC standards-based API for adding strong two-factor authentication to your web application. This API supports the Duo Universal Prompt, which uses a new OIDC-compliant authentication protocol to perform two-factor authentication.

If you need a solution that performs both primary and secondary authentication flows for OIDC applications, try Duo Single Sign-On for Generic OIDC Relying Parties.

Overview

Duo uses the OpenID Connect protocol to deliver two-factor authentication to users and make available the result of the two-factor authentication. The flow begins with a health check to Duo's service, followed by a request for authorization. After validating this request, Duo redirects the user to a Duo-hosted site for multi-factor authentication. Upon a successful second-factor authentication Duo redirects the user to the redirect URI supplied in the initial Authorization request. To retrieve the result of the second factor authentication from Duo the client will make a request to the Access Token endpoint. The response includes contextual information about the Duo authentication event.

Duo OIDC MFA Authentication Flow

The redirect URI is a critical part of adding support for the Duo Universal Prompt. After a successful two-factor authentication, the end-user must be sent to a page which will retrieve the result of the authentication. The redirect URI parameter in the JWT sent to Duo in the first authorization request tells Duo where to redirect the end-user. Duo’s use of an explicit authorization code flow for the OIDC spec sufficiently protects against attacks which redirect requests to URIs controlled by bad actors.

You may not load the OIDC-based Duo prompt in an iFrame on your site. The redirect to Duo to show the prompt and back is required.

The client can configure their desired behavior in the event of a failed health check to determine whether a user should be granted access to their protected resources or prevented from doing so.

JSON Web Tokens (JWTs) are used in each request and should be signed using the secret key you are provided when protecting an application.

NOTE: Adding Duo using the OIDC API requires custom development and some understanding of your application's language and authentication process.

To add Duo to your application using our client SDKs for Python, Java, Go, NodeJS, PHP, or C#, see the Duo Web SDK v4 instructions.

Unsupported OIDC Endpoints

Duo’s implementation of OIDC for two-factor authentication does not require and therefore does not support the following endpoints/code flows:

  1. UserInfo - Required for Primary.
  2. Discovery Endpoint - Required for Primary.
  3. Refresh - By design 2FA token should be valid only once.
  4. JWKS - Duo does not currently support asymmetric signing and thus does not have an endpoint to describe public keys and their rotations.

First Steps

To begin development with a new Duo Web SDK integration:

  1. Sign up for a Duo account.
  2. Log in to the Duo Admin Panel and navigate to Applications.
  3. Click Protect an Application and locate the 2FA-only entry for Web SDK in the applications list. Click Protect to the far-right to configure the application and get your Client ID, Client secret, and API hostname. You'll need this information to complete your setup. See Protecting Applications for more information about protecting applications in Duo and additional application options.

    Previously, the Client ID was called the "Integration key" and the Client secret was called the "Secret key".

  4. Use NTP to ensure that your server's time is correct.

Documented properties will not be removed within a stable version of the API. Once a given API endpoint is documented to return a given property, a property with that name will always appear (although certain properties may only appear under certain conditions).

New, undocumented properties may also appear at any time. For instance, Duo may make available a beta feature involving extra information returned by an API endpoint. Until the property is documented here its format may change or it may even be entirely removed from our API.

Universal Prompt

The Duo Universal Prompt provides a simplified and accessible Duo login experience for web-based applications, offering a redesigned visual interface with security and usability enhancements.

Universal Prompt Traditional Prompt
 Duo Push in Universal Prompt  Duo Push in Traditional Prompt

Migration to Universal Prompt for your Duo Web application is a two-step process:

  • Build your application with support for the Universal Prompt by using the OIDC Auth API.
  • Activate the Universal Prompt experience for users of that Duo Web application.

If you're creating a Duo Web application for the first time, building it with the OIDC Auth API or Duo Web v4 SDK ensures it supports the Universal Prompt.

Duo Prompt UI Support per Delivery Method

OIDC Redirect (Web SDK v4) Iframe (Web SDK v2)
Universal Prompt YES NO
Traditional Prompt YES YES

New Web SDK Applications

The "Universal Prompt" section on the details page of your new Duo Web SDK application shows the status as "Ready to activate", with these activation control options:

  • Show traditional prompt: (Default) Your users experience Duo's traditional prompt when logging in to this application.

  • Show new Universal Prompt: Your users experience the Universal Prompt when logging in to this application.

Universal Prompt Info - Application Ready for Universal Prompt

Existing Web SDK Applications

The "Universal Prompt" section on the details page of your existing Duo Web v2 iframe application will show the status as "Updated required" with a link to update instructions if an application software update is available.

Universal Prompt Info - Update Required

Once you update your Duo integration to use OIDC Auth API or Web SDK v4, and a user authenticates to that existing application via the frameless OIDC-based prompt, the "Universal Prompt" section of the Duo Web application page reflects a status change to "Ready to activate", with these activation control options:

  • Show traditional prompt: (Default) Your users experience Duo's traditional prompt when logging in to this application.

  • Show new Universal Prompt: Your users experience the Universal Prompt when logging in to this application.

Universal Prompt Info - Application Ready for Universal Prompt

In addition, the "Integration key" and "Secret key" property labels for the application update to "Client ID" and "Client secret" respectively. The values for these properties remain the same.

Activate Universal Prompt

Activation of the Universal Prompt is a per-application change. Activating it for one application does not change the login experience for your other Duo applications.

Enable the Universal Prompt experience by selecting Show new Universal Prompt, and then scrolling to the bottom of the page to click Save.

Once you activate the Universal Prompt, the application's Universal Prompt status shows "Activation Complete" here and on the Universal Prompt Update Progress report.

Universal Prompt Info - Universal Prompt Activation Complete

Should you ever want to roll back to the traditional prompt, you can return to this setting and change it back to Show traditional prompt.

Universal Update Progress

Click the See Update Progress link to view the Universal Prompt Update Progress report. This report shows the update availability and migration progress for all your Duo applications that will have Universal Prompt support. You can also activate the new prompt experience for multiple supported applications from the report page instead of visiting the individual details pages for each application.

Watch the Duo Blog for future updates about the Duo Universal Prompt.

JSON Web Tokens

JSON Web Tokens (JWTs) are the secure mechanism with which Duo will verify that the request is coming from a trusted client application. More information about the structure of JWTs can be found at JWT.IO and RFC 7519.

JWTs are encoded as a single string, but when decoded have three sections: the header, the payload, and the signature.

Example of an encoded JWT

Encoded JWT

Example of the encoded JWT above decoded and separated into each section

Decoded JWT

In the header, Duo requires that the typ field is equal to JWT and that the alg field is either HS256 (HMAC using SHA-256) or HS512 (HMAC using SHA-512). We also require use of the HS256 or HS512 HMAC algorithms to sign the request.

The signature for each JWT should use the client secret value (also shown as the "secret key" depending on when you created your Duo Web SDK application) that you can find on your integration’s page in the Admin Panel.

For each OIDC endpoint there exists a required field for supplying a JWT. Each JWT will have the same requirements for the header and signature, but the payload data shape has differing format described as the field name followed by .payload in the endpoint details below. For example, in the Health Check endpoint /oauth/v1/health_check we require that a client_assertion parameter is provided with a value of type JWT. We denote the shape of that JWT payload with the parameter name of client_assertion.payload.

Please note that all Unix timestamps are in seconds.

Endpoints

Health Check

To ensure that all Duo services are operating we require a call to a health check endpoint with arguments that enable us to verify your integration is properly configured.

POST/oauth/v1/health_check

Arguments

Argument Type Required? Description
client_id String Required This value was previously referred to as the integration key. Locate the value on the application's page in the Duo Admin Panel.
client_assertion JWT Required See below for the expected client_assertion payload.
client_assertion.payload Object Required
Argument Type Required? Description
iss String Required The Client ID (or Integration key) from the application's page in the Duo Admin Panel.
sub String Required This should match the above iss value.
aud String Required This field must match the expected base URL where the request was sent, using the "API hostname" from the application's page in the Duo Admin Panel: https://{api_hostname}/oauth/v1/health_check.
exp String Required The time at which the request you are sending should expire. Duo recommends an exp value of five minutes. The timestamp format should be in seconds from Unix epoch.
jti String Required The jti value adds randomization to the JWT calculation. This value needs to be unique for each JWT calculation and sufficiently random to prevent attacks.
iat Integer Optional The time at which the JWT was created.

Example Response

Success:

{
  "stat": "OK",
  "response": {
     "timestamp": {int32} The time at which the health check occurred.
  }
}

Failure:

{
  "stat": "FAIL",                  
  "code": {String} An API error code which corresponds to our existing API error documentation,
  "timestamp": {int32} The time at which the health check occurred,
  "message": {String} The failure error message,
  "message_detail": {String} Additional information about the error.
}

Authorization Request

This endpoint processes your application’s request for Duo to perform multi-factor authentication and sends the end-user to the Duo-hosted multi-factor authentication prompt. We require various fields to be sent in a JWT with this request in order to determine the validity of the request.

After Duo processes this request, this endpoint will return a response that redirects the end user to a Duo hosted domain to perform multi-factor authentication. This is the same domain that was previously used as the source of the iframe that delivered the authentication prompt.

In the event that multi-factor authentication is unsuccessful, we will not redirect the end-user to the specified redirect URI. A failed authentication will appear in the Authentication Log (found in the Admin Panel) associated with the end-user.

After a successful authentication, Duo redirects the user to the redirect URI specified in the redirect_uri field as described below.

GET/oauth/v1/authorize
POST/oauth/v1/authorize

GET and POST requests require the same parameters and have the same expected response.

Arguments

Argument Type Required? Description
response_type String Required We expect the value code to be present for this field.
client_id String Required This value was previously referred to as the integration key. Locate the value on the application's page in the Duo Admin Panel.
request JWT Required See below for the expected request payload.
redirect_uri String Optional The URI where the end-user will be redirected after a successful auth. While supplying this value in the query parameters is optional, if provided it should match the value provided in the JWT payload. Must be well-formed with a valid HTTPS URL and port, using an RFC-compliant hostname. Maximum length: 1024 characters.
scope String Optional The scope must be openid. A different value or multiple values will fail.
nonce String Optional If provided, this must be a random value that is cryptographically secure such that it is unguessable by a third party. We require a length of at least 16 characters and at most 1024 characters.
state String Optional A random, cryptographically secure value that the client must maintain throughout the authentication. We require a length of at least 16 characters and at most 1024 characters.
request.payload Object Required
Argument Type Required? Description
response_type String Required We expect the value code to be present for this field.
scope String Required Must match the value defined above.
exp String Required The time at which the request you are sending should expire. Duo recommends an exp value of five minutes. The timestamp format should be in seconds from Unix epoch.
client_id String Required The Client ID (or Integration key) from the application's page in the Duo Admin Panel.
redirect_uri String Required The URI where the end-user will be redirected after a successful auth. Must be well-formed with a valid HTTPS URL and port, using an RFC-compliant hostname. Maximum length: 1024 characters.
state String Required A random, cryptographically secure value that the client must maintain throughout the authentication. We require a length of at least 16 characters and at most 1024 characters.
duo_uname String Required Unique name for the user in Duo. If a user has aliases in Duo, those are valid to send in this field. Subject to username normalization as configured for the application in the Admin Panel.
iss String Optional If provided, this field must match the client_id sent in this payload.
aud String Optional This field must match the "API hostname" from the application's page in the Duo Admin Panel: https://{api_hostname}.
nonce String Optional If provided, this must be a random value that is cryptographically secure such that it is unguessable by a third party. We require a length of at least 16 characters and at most 1024 characters.
use_duo_code_attribute Boolean Optional If provided and the value is true, then the authorization code will be returned under the attribute name of duo_code.

We require that the state parameter be sent in either the JWT payload OR in the request query parameters. If the state parameter is present both in the JWT payload and in the request query parameters, we will honor the value sent in the request query parameters. Additionally, while the nonce parameter is optional, if it is sent in both the JWT payload AND the request query parameters, we will honor the value sent in the request query parameters.

Access Token

The success response to the redirect URI sent previously will have the following structure:

{
  code {String}: A randomly generated authorization code that will be used to retrieve
                 the result of the authentication,
  state {String}: This should be the value for state passed in to the original request.
                  It is up to the client to verify that it is the same value as a security
                  measure. This value specifically protects against CSRF attacks (see RFC 6749).
}

This code can then be exchanged for an ID Token that includes contextual information about the two-factor authentication via a POST request to the Token Endpoint.

If the custom claim of use_duo_code_attribute was sent with a value of true in the authorization request JWT, then the authorization code parameter name will be duo_code instead of code.

POST/oauth/v1/token

Arguments

Argument Type Required? Description
grant_type String Required This must be equal to the string authorization_code.
code String Required This must be equal to the string code received in the previous response.
redirect_uri String Required This must be equal to the redirect URI sent in the request to the Authorization Request endpoint /oauth/v1/authorize.
client_assertion_type String Required This must be equal to the string value urn:ietf:params:oauth:client-assertion-type:jwt-bearer,
client_assertion JWT Required See below for the required payload fields.
client_id String Optional The Client ID (or Integration key) from the application's page in the Duo Admin Panel.
client_assertion.payload Object Required
Argument Type Required? Description
iss String Required The Client ID (or Integration key) from the application's page in the Duo Admin Panel.
sub String Required This should match the above iss value.
aud String Required This field must match the expected base URL where the request was sent, using the "API hostname" from the application's page in the Duo Admin Panel: https://{api_hostname}/oauth/v1/token.
exp Integer Required The time at which the request you are sending should expire. Duo recommends an exp value of five minutes. The timestamp format should be in seconds from Unix epoch.
jti String Required The jti value adds randomization to the JWT calculation. This value needs to be unique for each JWT calculation and sufficiently random to prevent attacks.
iat Integer Optional The time at which the JWT was created.

The response from the Token Endpoint includes an ID token, an access token, and contextual information about the authentication. The access token can not be exchanged for further access because Duo is not a first-factor identity provider.

Response Format

The response follows the structure below:

{
    id_token {JWT}: See below for id_token payload structure,
      access_token {String}: A random and unique identifier for the authentication session,
      expires_in {integer}: The time at which the access token is considered to be expired,
      token_type {String}: This will be equal to the string "Bearer".
}
id_token.payload Object
iss String A URI which details the endpoint that the response came from, with a hostname that should match your Duo application's API hostname.
sub String Equal to the duo_uname of the end-user who authenticated.
preferred_username String Equal to the username sent as duo_uname to the Authorization Request endpoint.
aud String Equal to the Client ID (or Integration key) from the application's page in the Duo Admin Panel.
exp Integer The time at which this JWT expires.
iat Integer The time at which the JWT was issued.
auth_time Integer The time at which this end-user authenticated with Duo.
nonce String The random string generated during the initial authorization request.
auth_result Object
result String This will be equal to the string allow.
status String This will be equal to the string allow.
status_msg String A string describing why the authentication was successful.
auth_context Object Please see the Duo Authentication Logs reason values for the full authentication log information.

Duo highly recommends that you perform the ID Token validation steps outlined in the OIDC Specification. Additionally, if your implementation maintains stateful information about the user authenticating you should validate that the user described in the ID token matches your expected user. For example, if you have stored stateful information with the state parameter that user jdoe is authenticating, you should verify that the ID Token specifically has the username jdoe in the preferred_username field.

Example Response

Note that the information returned in the response may vary depending on the Duo edition used and effective policies for the authentication. This example is from Duo Premier edition.

{
  "aud": "DI0P00LD4DX50O2VCAVK",
  "auth_context": {
    "access_device": {
      "browser": "Chrome",
      "browser_version": "128.0.6613.120",
      "epkey": "EP2ULFE874LMXBYJZE4D",
      "flash_version": "uninstalled",
      "hostname": null,
      "ip": "12.106.124.163",
      "is_encryption_enabled": true,
      "is_firewall_enabled": true,
      "is_password_set": true,
      "java_version": "uninstalled",
      "location": {
        "city": "Detroit",
        "country": "United States",
        "state": "Michigan"
      },
      "os": "Mac OS X",
      "os_version": "14.6.1",
      "security_agents": [
        {
          "security_agent": "cisco-amp",
          "version": "1.24.0.967"
        }
      ]
    },
    "adaptive_trust_assessments": {
      "more_secure_auth": {
        "detected_attack_detectors": [
          "NOVEL_ASN"
        ],
        "features_version": "3.0",
        "model_version": "2022.07.19.001",
        "policy_enabled": false,
        "preview_mode_enabled": true,
        "reason": "Low level of trust; detection of one or more known attack patterns",
        "trust_level": "LOW"
      },
      "remember_me": {
        "features_version": "3.0",
        "model_version": "2022.07.19.001",
        "policy_enabled": false,
        "reason": "Novel Access IP",
        "trust_level": "LOW"
      }
    },
    "alias": "",
    "application": {
      "key": "DI0P00LD4DX50O2VCAVK",
      "name": "Acme Corp"
    },
    "auth_device": {
      "ip": "12.106.124.163",
      "key": "DP5IWE1PCVP00LD4D64A",
      "location": {
        "city": "Detroit",
        "country": "United States",
        "state": "Michigan"
      },
      "name": "Norben's Phone"
    },
    "email": "narroway@example.com",
    "event_type": "authentication",
    "factor": "duo_push",
    "isotimestamp": "2024-09-10T18:57:44.444611+00:00",
    "ood_software": null,
    "rbfs_triggered_attacks": [
      {
        "country_code_access": null,
        "country_code_auth": null,
        "detected_occurrences": 1,
        "detector": "NOVEL_ASN",
        "shadow_mode": false,
        "suppressed": null,
        "threshold_occurrences": 1,
        "threshold_time_frame": null,
        "unrealistic_travel_velocity": null
      }
    ],
    "reason": "user_approved",
    "result": "success",
    "timestamp": 1725994664,
    "trusted_endpoint_status": "unknown",
    "txid": "08bfd78a-7b70-4818-af15-104744ab3609",
    "user": {
      "groups": [],
      "key": "DU53P00LD4D4C075WQGK",
      "name": "narroway"
    }
  },
  "auth_result": {
    "result": "allow",
    "status": "allow",
    "status_msg": "Login Successful"
  },
  "auth_time": 1725994664,
  "exp": 1725998264,
  "iat": 1725994667,
  "iss": "https://api-nnnnnnnn.duosecurity.com/oauth/v1/token",
  "preferred_username": "narroway",
  "sub": "narroway"
}

Troubleshooting

Need some help? Take a look at our OIDC Auth API Knowledge Base articles or Community discussions. For further assistance, contact Support.