Authentication
Access Tokens
The Oauth2 service generates access tokens for authenticated users, applications or companies. The token returned in the Oauth2 response can be used to access protected resources on SAP Concur services.
The Oauth2 response can, depending on grant type contain these values:
Name | Type | Format | Description |
---|---|---|---|
expires_in |
string |
- | The lifetime in seconds of the access token |
scope |
string |
- | The scope of the access token as granted to the client application |
token_type |
string |
- | The type of token returned. Value will be Bearer |
access_token |
string |
- | Token used to access protected resources of SAP Concur services. |
refresh_token |
string |
- | Refresh token required to request a new access token for a given user. |
geolocation |
string |
- | The base URL for where the user profile lives. See base URI for usage. |
id_token |
string |
- | The OIDC Token in the JSON Web Token (JWT) format that describes the user or company |
Token Response
HTTP/1.1 200 OK
Content-Type: application/json
Date: date-requested
Content-Length: 3397
Connection: Close
{
"expires_in": "3600",
"scope": "app-scopes",
"token_type": "Bearer",
"access_token": "access_token",
"refresh_token": "refresh_token",
"id_token": "oidc_token",
"geolocation": "https://us.api.concursolutions.com"
}
Obtaining a token
You can obtain a token for three different types of principals in the SAP Concur universe.
- User
- Application
- Company
Token Lifetime
An accessToken
has a one hour lifetime.
In order to obtain a token, the client application needs to call the Oauth2 endpoint using various grants depending on the authentication scenarios required. The full list of supported scenarios is provided below:
Refreshing a token
The refresh grant is used to refresh an access_token that has expired. This grant can be used anytime a refresh_token is returned in the response of another grant request. No user interaction is required.
Token Lifetime
A refresh token has a six month lifetime. If the refresh token expires, the client application must reinitiate the authorization process. When a refresh token is used to request a new access token, both a new access token as well as a new refresh token are returned in the response. This token can change even if most of the time, this value is the same. Client applications should treat all returned refresh tokens as new tokens and overwrite the stored tokens with the new token from the response.
It is recommended that the client application use the refresh grant to request a new access token as the initial step of accessing protected resources of SAP Concur services.
Refreshing the token
To request a new access token using a valid refresh token, use the Oauth2 /token endpoint. Use the application/x-www-form-urlencoded
content type.
POST /oauth2/v0/token
Post Body
Name | Type | Format | Description |
---|---|---|---|
client_id |
string |
UUID |
Required The client applications client_id supplied by App Management |
client_secret |
string |
UUID |
Required The client applications client_secret supplied by App Management |
refresh_token |
string |
- | Required An existing valid refresh token to be used to request a new access token |
scope |
string |
- | Optional The client applications list of scopes |
grant_type |
string |
- | Required The grant type instructs the Oauth2 service how to process the request. For refresh token, the value must be refresh_token |
Request
POST /oauth2/v0/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: us.api.concursolutions.com
Connection: close
Content-Length: 437
POST BODY
client_id=your-client_id
&client_secret=your-client_secret
&grant_type=refresh_token
&refresh_token=valid-refresh_token
&scope=app-scope
Response
HTTP/1.1 200 OK
Content-Type: application/json
Date: date-requested
Content-Length: 3397
Connection: Close
{
"expires_in": "3600",
"scope": "app-scopes",
"token_type": "Bearer",
"access_token": "access_token",
"refresh_token": "refresh_token",
"id_token": "oidc_token",
"geolocation": "https://us.api.concursolutions.com"
}
When the token has been refreshed, store the geolocation and the refresh token as they may have changed. On subsequent calls, use the last received geolocation and refresh token.
Revoking a token
All refresh tokens associated to a user for an application can be revoked by calling the https://us.api.concursolutions.com/app-mgmt/v0/connections
endpoint with a DELETE
action. You have to provide the User’s accessToken
in the Authorization Header as Authorization: Bearer <access_token>
.
DELETE https://us.api.concursolutions.com/app-mgmt/v0/connections
Request
DELETE https://us.api.concursolutions.com/app-mgmt/v0/connections
Authorization: Bearer {token}
Response
HTTP/1.1 200 OK
Managing tokens
Refresh Tokens are strings that allow your application to obtain a fresh accessToken
on behalf of a user to access SAP Concur APIs. The exact format of the string can change, but may look similar to the following:
e013335d-b4ce-4c43-a7e4-b67abc1adcb0
or like this:
2d725xipty0z7ha3vlpy8b2c3hqxmw
It is highly recommended that you store Refresh Tokens together with your user’s authorization metadata in your application every time you obtain a new refreshToken
as they might change depending on different scenarios.
FOR APP CENTER AND SUPPLIER PARTNERS supporting all geolocations, storing the authorization metadata, including the geolocation are REQUIRED.
Base URIs
When making API calls, the appropriate base URI should be used. There are three different scenarios:
- Obtaining a token for a user.
- Refreshing a token.
- Calling other APIs.
The base URI for obtaining a token will leverage your application’s geolocation. The base URI for refreshing tokens and all other API calls will leverage the token’s geolocation.
Base URIs for Obtaining a Token
When your application is created, you will be provided with a client ID, secret and geolocation. When obtaining a token, your application should use the base URI for the geolocation in which your application exists.
There are two endpoints for each geolocation - one is the default (used for server-side calls) and the other should be used for client-side calls.
The full list of available token geolocations is available on the Base URIs page.
When obtaining the token, the token’s geolocation will be included in the response. The token’s geolocation should be stored along with the token. The Developer’s app will then be able to make subsequent calls using the token and the correct end points based on the token’s GEO location.
Base URIs for All Other Calls
When refreshing a token or when calling any other APIs, the token’s geolocation should be used as the base URI.
Note: Client-side calls should use the www- variant of the base URI.
Example
Consider an example where your application obtains a token and receives the response below:
HTTP/1.1 200 OK
Content-Type: application/json
Date: date-requested
Content-Length: 3397
Connection: Close
{
"expires_in": "3600",
"scope": "app-scopes",
"token_type": "Bearer",
"access_token": "access_token",
"refresh_token": "refresh_token",
"id_token": "oidc_token",
"geolocation": "https://us.api.concursolutions.com"
}
When your application calls another API, such as the receipts API to post a receipt, the request should be made using the base URI specified in the geolocation value of the response. In the example above, that request would be made to https://us.api.concursolutions.com (if server side) or https://www-us.api.concursolutions.com (for clients). The geolocation value can be any of the supported base URIs. The application should store the specific geolocation it receives, and use it as the base URI for future API calls when using that token.
ID Token
Authentication service will return an OPENID compatible ID token with every token request. This id_token
is primarily used to describe information about a user or a company. You can obtain the userId from this token.
Sample id_token:
{
"aud": "e010e25d-b4ce-4ce3-a7e4-b670cb1adcb0",
"concur.profile": "https://us.api.concursolutions.com/profile/v1/principals/76459ad3-f77b-4d98-a21a-55333c9179f0",
"concur.version": 2,
"concur.type": "user",
"sub": "76459ad3-f77b-4d98-a21a-55333c9179f0",
"iss": "https://us.api.concursolutions.com",
"exp": 1485485529,
"nbf": 1485481929,
"at_hash": "351515a6482f4ee1",
"iat": 1485481929
}
Verifying an id_token
The Authentication service exposes JWKs that can be used to validate the id_token in the form of a JWT. Validating a JWT is described in detail in RFC 7519 - sec 7.2
This is the link to the SAP Concur JSON Web Key for Oauth2. https://www-us.api.concursolutions.com/oauth2/v0/jwks
Authorization grant
The authorization grant is the regular 3-legged oauth2 grant and is defined in detail in RFC6749 sec-4.1. This grant requires the user to explicitly authenticate themselves and authorise the application initiating the grant.
The users must be able to authenticate themselves via an SAP Concur username & password. Users will be challenged to login by an Oauth2 HTML page.
Who should use it
- 3rd party “partner” websites - or -
- non-SAP Concur Applications - & -
- Applications that need explicit user authentication & authorization - & -
- Applications that can securely store a code, access_token & refresh_token
Grant details
Note that the grant type must be accessed using the
www-
version of the API Gateway in order to avoid certificate issues with some browsers. (ex: https://www-us.api.concursolutions.com instead of https://us.api.concursolutions.com)
GET /oauth2/v0/authorize
Name | Type | Format | Description |
---|---|---|---|
client_id |
string |
UUID |
Applications client_id supplied by App Management |
redirect_uri |
string |
- | The redirect URI for your application to continue with the Oauth2 flow |
scope |
string |
- | List of scopes that application is asking for |
response_type |
string |
- | code |
state |
string |
- |
With this grant, the user has two authentication options:
- Username and password
- One-time link using a verified email address
With both options, once the user is successfully authenticated and the user authorizes your application, the user will be redirected to the redirect_URI specified in the initial /authorize call with a temporary token appended.
<redirect_uri>?geolocation=<token_geolocation>&code=<token>
If the user is not successfully authenticated or does not authorize the scopes for your application, an error code and description will be appended to the redirect URI. Please refer to the Response Codes section for more information.
Your application must then exchange the temporary token for a long-lived token using the below.
POST /oauth2/v0/token
Name | Type | Format | Description |
---|---|---|---|
client_id |
string |
UUID |
Applications client_id supplied by App Management |
client_secret |
string |
UUID |
Applications client_secret supplied by App Management |
redirect_uri |
string |
- | The redirect_uri that is registered for the application |
code |
string |
UUID |
The authorization code provided by Auth |
grant_type |
string |
- | authorization_code |
Password grant
The Password grant can be used when there is a trust relationship between the user and the application. There are two credential types allowed with Password Grant:
- “Password”: with this credential type, the application either already has the user’s credentials or can obtain the user’s credentials by directly interacting with the user.
- “AuthToken”: This credential type is used for connections from the App Center. For App Center partners and TripLink suppliers, please refer to the certification documentation for more information.
Post Body
Name | Type | Format | Description |
---|---|---|---|
client_id |
string |
UUID |
Applications client_id supplied by App Management |
client_secret |
string |
UUID |
Applications client_secret supplied by App Management |
grant_type |
string |
- | Specify which grant type you expect the oauth2 service to process. for password grant, the value is password |
username |
string |
- | specify the username or userId |
password |
string |
- | specify the user’s password |
credtype |
string |
- | The credtype signifies to oauth2 which credential set is being submitted in the request. There are two supported values: authtoken and password . For connections from the App Center, use authtoken . if omitted, oauth2 will assume the type is password . |
Request
POST /oauth2/v0/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: us.api.concursolutions.com
Connection: close
Content-Length: 175
POST BODY
client_id=your-client_id
&client_secret=your-client_secret
&grant_type=password
&username=username
&password=password
Response
HTTP/1.1 200 OK
Content-Type: application/json
Date: date-requested
Content-Length: 3397
Connection: Close
{
"expires_in": "3600",
"scope": "app-scopes",
"token_type": "Bearer",
"access_token": "access_token",
"refresh_token": "refresh_token",
"id_token": "oidc_token",
"geolocation": "https://us.api.concursolutions.com"
}
example bad login
{
"error": "invalid_grant",
"error_description": "Incorrect Credentials. Please Retry",
"code": 5
}
Client Credentials grant
Use the application/x-www-form-urlencoded
content type.
POST /oauth2/v0/token
Post Body
Name | Type | Format | Description |
---|---|---|---|
client_id |
string |
UUID |
Required Applications client_id supplied by App Management |
client_secret |
string |
UUID |
Required Applications client_secret supplied by App Management |
grant_type |
string |
- | Required Specify which grant type you expect the oauth2 service to process. For client_credentials grant, the value is client_credentials |
Request
POST /oauth2/v0/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: us.api.concursolutions.com
Connection: close
Content-Length: 127
POST BODY
client_id=your-client_id
&client_secret=your-client_secret
&grant_type=client_credentials
Response
HTTP/1.1 200 OK
Content-Type: application/json
Date: date-requested
Content-Length: 1626
Connection: Close
{
"expires_in": "3600",
"scope": "app-scopes",
"token_type": "Bearer",
"access_token": "access_token",
"geolocation": "https://us.api.concursolutions.com"
}
One Time Password grant
The One-time Password grant type leverages email, phone (text messaging), instant messaging and similar systems to provide per user access tokens to client applications. This grant type requires the following steps:
- The calling application calls the OAuth2 service specifying the otp grant type along with required parameters.
- The OAuth2 service generates a one time token which it sends through the messaging mechanism chosen by the application.
- The user retrieves the token and presents it to the application. The means of having this presented to the application is the responsibility of the application.
- The application presents this one-time token to the OAuth2 service via the token endpoint.
Request a one-time token to be sent to the user
Use the application/x-www-form-urlencoded
content type.
POST /oauth2/v0/otp
Post Body
Name | Type | Format | Description |
---|---|---|---|
client_id |
string |
UUID |
Required The client_id as defined in the SAP Concur application management system. |
client_secret |
string |
UUID |
Required The client_secret as set by the client owner in the SAP Concur application management system. |
channel_handle |
string |
- | Required The location (email address, phone number) where the one time token should be sent. Currently, only email address is valid. |
channel_type |
string |
- | Required The type of messaging system to use. Currently only email is valid |
name |
string |
- | Optional The name of the user that appears in the email. |
company |
string |
- | Optional The company or application name that appears in the email. |
link |
string |
- | Optional The callback URL that appears in the email for users to click to complete the auth flow. |
The calling application code can also append n-number of unique client defined parameters in the URI for the purpose of connecting the one time token to the application when received by the user. The names of these parameters cannot conflict with the API defined parameters.
The following are reserved words and cannot be used as client application defined parameters:
/otp: "client_id" "client_secret" "channel_type" "channel_handle"
/token: "client_id" "client_secret" "channel_type" "channel_handle" "scope" "grant_type" "otp"
If the calling application chooses to send custom parameters, all of these exact same parameters must be included in subsequent API calls to acquire the access token. In other words, the URI signature, modulo the one time token parameter itself and token service specific parameters, must match the originating URI signature.
Request
POST /oauth2/v0/otp HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Accept: application/json
Host: us.api.concursolutions.com
Connection: close
Content-Length: 437
POST BODY
client_id=your-client_id
&client_secret=your-client_secret
&channel_handle=email adress
&channel_type=valid-email
&link=https://example.com/callback
Response
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 22
Date: date-requested
{
"message": "otp sent"
}
Request an access token
The One Time Password grant requires that all of the parameters, including client application defined parameters to be sent in the request body when requesting an access token. Use the application/x-www-form-urlencoded
content type.
POST oauth2/v0/token
Post Body
Name | Type | Format | Description |
---|---|---|---|
client_id |
string |
UUID |
Required The client_id as defined in the SAP Concur application management system. |
client_secret |
string |
UUID |
Required The client_secret as set by the client owner in the SAP Concur application management system. |
channel_handle |
string |
- | Required The location (email address, phone number) where the one time token should be sent. |
channel_type |
string |
- | Required The type of messaging system to use. Currently only email is valid |
scope |
string |
- | The scope(s) requested by the client for the token. |
grant_type |
string |
- | Required The grant type being used, specifically for this approach: otp . |
otp |
string |
- | Required The one-time token provided as a result of the Send a one time token to the user method. |
Request
POST /oauth2/v0/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: us.api.concursolutions.com
Connection: close
Content-Length: 437
POST BODY
client_id=your-client_id
&client_secret=your-client_secret
&channel_handle=email adress
&channel_type=valid-email
&scope=app_scope
&grant_type=otp
&otp=one-time-token
Response
HTTP/1.1 200 OK
Date: date-requested
Content-Length: 1490
Connection: keep-alive
{
"expires_in": "3600",
"scope": "app-scopes",
"token_type": "Bearer",
"access_token": "access_token",
"refresh_token": "refresh_token",
"id_token": "oidc_token",
"geolocation": "https://us.api.concursolutions.com"
}
Response Codes
HTTP Status returned by oauth2
HTTP Status | Description |
---|---|
200 | OK - Successful call, response is in body. |
400 | Bad Request (error, error_description, code) |
401 | Unauthorized (error, error_description, code) |
403 | Forbidden (error, error_description, code) |
404 | Not Found (error, error_description, code) |
500 | Server Error, error message is in body. |
503 | Server Timed Out, error message is in body. |
4xx class errors have a JSON response with the following fields
{
"code": <number>,
"error": <error>,
"error_description": <error_description>,
"geolocation": <geolocation url where user lives>
}
/authorize
If the authorization or authentication are unsuccessful, your application will receive an error code and description at the redirect_uri provided.
error_code=<>
&error_description=<>
In all cases, the friendly error description should be displayed to the user.
/token
Code | Error | Description |
---|---|---|
5 | invalid_grant |
Incorrect credentials. Please Retry |
10 | invalid_grant |
Account is disabled. Please contact support |
11 | invalid_grant |
Account is disabled. Please contact support |
12 | invalid_grant |
Logon Denied. Please contact support |
13 | invalid_grant |
Logon Denied. Please contact support |
14 | invalid_grant |
Account Locked. Please contact support |
16 | invalid_request |
user lives elsewhere |
19 | invalid_grant |
Incorrect credentials. Please Retry |
20 | invalid_grant |
Logon Denied. Please contact support (typically due to IP restriction) |
21 | invalid_request |
Incorrect credentials. SSO-only client attempted a password login. |
51 | invalid_request |
username was not supplied |
52 | invalid_request |
password was not supplied |
53 | invalid_client |
company is not enabled for this client |
54 | invalid_scope |
requested scope exceeds granted scope |
55 | invalid_request |
we don’t know this email |
56 | invalid_request |
otp was not supplied |
57 | invalid_request |
channel_type missing |
58 | invalid_request |
channel_handle missing |
59 | access_denied |
client disabled |
60 | invalid_grant |
these are not the grants you are looking for |
61 | invalid_client |
client not found |
62 | invalid_request |
client_id was not supplied |
63 | invalid_request |
client_secret was not supplied |
64 | invalid_client |
Incorrect credentials. Please Retry |
65 | invalid_request |
grant_type was not supplied |
80 | invalid_request |
invalid channel type |
81 | invalid_request |
bad channel handle |
83 | invalid_request |
otp not found |
84 | invalid_request |
fact verification failed |
85 | invalid_request |
otp verification failed |
100 | invalid_request |
backend does not know about this username |
101 | invalid_request |
code was not supplied |
102 | invalid_request |
redirect_uri was not supplied |
103 | invalid_request |
code is bad or expired |
104 | invalid_grant |
redirect_uri does not match the previous grant |
105 | invalid_grant |
this grant was not issued to you! |
106 | invalid_request |
refresh_token was not supplied |
107 | invalid_request |
refresh disallowed for app |
108 | invalid_grant |
bad or expired refresh token |
109 | invalid_request |
loginid was not supplied |
115 | invalid_request |
unauthenticated client will not be issued token! |
117 | invalid_request |
nonce is mandatory for this response_type |
118 | invalid_request |
display is invalid |
119 | invalid_request |
prompt is invalid |
119 | invalid_request |
prompt must be set to consent for offline_access |
120 | invalid_request |
credtype is invalid |
121 | invalid_request |
login_type is invalid |
122 | invalid_request |
proxies supplied are invalid |
123 | invalid_request |
principal is disabled |
124 | invalid_request |
product is invalid |
135 | invalid_request |
unsupported request format |
136 | invalid_request |
Authtoken was not issued for you |
139 | invalid_request |
Logon Denied. Password must be changed to meet company policy. |
/otp
Code | Error | Description |
---|---|---|
16 | invalid_request |
user lives elsewhere |
57 | invalid_request |
channel_type was not supplied |
58 | invalid_request |
channel_handle was not supplied |
60 | invalid_grant |
these are not the grants you are looking for |
61 | invalid_client |
client_id is not known to us |
62 | invalid_request |
client_id was not supplied |
63 | invalid_request |
client_secret was not supplied |
80 | invalid_request |
invalid channel type |
81 | invalid_request |
bad channel handle |
82 | invalid_request |
the number of open otp requests has been exceeded |
135 | invalid_request |
unsupported request format |
Troubleshooting
In order to assist with troubleshooting, SAP Concur responds with a unique correlationId in the response header. The key to look for is correlationid
. This unique code can be used during troubleshooting as it identifies the API call in the log files. You should record this information in your own API call logs as well so that you can pass this information on to the SAP Concur support team.
Example of the correlationid
in the response:
< HTTP/1.1 200 OK
< Server: cnqr-papeete
< Date: Mon, 04 Dec 2017 22:07:05 GMT
< Content-Type: application/json
< Content-Length: 2897
< Connection: keep-alive
< Concur-Correlationid: 2803b8f8-a42b-43c2-a739-b8768e4759b8
Enterprise Business Applications
Only the Password Grant Type is available for obtaining company-level tokens.
- To begin the authentication flow, a Customer’s SAP Concur Administrator clicks on the Connect button within the App Center listing and authorizes the partner’s app. This app listing is located within customer’s SAP Concur system’s App Center tab.
- The SAP Concur authorization service will redirect the Admin to the Partner’s Landing Page. Partners should follow the App Center UX Guidelines to create a web page that listens for an HTTP GET request from SAP Concur.
- The redirect URI will contain an id, requestToken and userId parameters. Example:
https://{partner_redirect_URI}?id=8568a4cd-8ffc-49d6-9417-be2d69aa075f&requestToken=5l85ae5a-426f-4d6f-8af4-08648c4b696b&userId=9bdded51-00b8-4f84-8bef-6d3afe727007
- When the Partner application receives the redirect call, the Partner should strip the
id
andrequestToken
values from the URI and use those on a Post request to the SAP Concur Authorization service to obtain the official Oauth2 Access Token and Refresh Token for the customer using the password grant. We currently have 3 Data Centers and the API end points change based on these Data Centers so it is imperative the proper token management is followed. Otherwise, your app will not make the correct call per Access token. - An access token is valid for only one hour. The access token should be cached in memory and discarded after use.
- After the Admin has successfully completed the login/enrollment process, the Partner should store the following elements with the customer’s profile metadata.
refresh_token
: Valid for six months from the day and time issued.refresh_expires_in
: This is Epoch time format, convert to UTC.geolocation
: To be used when making API calls on behalf of the customer.id
: Akasub
, is the customer’s unique identifier (UUID). It can be retrieved from the following sources:
- From the re-direct URI as the id element.
- By decoding the
id_token
returned with Access token, as thesub
element. (See https://jwt.io)
- It is highly recommended that Partners log the following elements:
userId
: the user who clicked on the Connect button (returned in the re-direct URI)correlationid
: SAP Concur responds with a unique code which identifies the API call in the log files. (returned in the response header). More details can be found here.