I am getting invalid_scope error in access token request for client credential flow. The error log states that "cannot request OpenID scopes in client credentials flow". I haven't requested for the open id scope. I don't know from where it came from. I need to generate access token using client credential flow.
Issue / Steps to reproduce the problem
API Resource definition.
public IEnumerable GetApiResources()
{
return new List {
new ApiResource
{
Name = "WidgetApi",
DisplayName = "Widget Management API",
Description = "Widget Management API Resource Access",
ApiSecrets = new List { new Secret("scopeSecret".Sha256()) },
Scopes = new List {
new Scope("WidgetApi.Read"),
new Scope("WidgetApi.Write")
}
}
};
}
Client Definition;
return new List
{
new Client
{
ClientId = "WidgetApi Client Id",
ClientName = "WidgetApi Client credential",
RequireConsent = false,
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets =
{
new Secret( clientSecret.Sha256())
},
// scopes that client has access to
AllowedScopes = { "WidgetApi.Read", "WidgetApi.Write"},
AccessTokenLifetime = 3600
};
}
Access token request body (key - value) using postman
grant_type client_credentials
response_type id_token
scope WidgetApi.Read WidgetApi.Write
client_secret xxxxxxxxxxxxxxxxxxxxxx
client_id WidgetApiClientId
Relevant parts of the log file
dbug: Microsoft.EntityFrameworkCore.Storage.Internal.SqlServerConnection[4]
Closing connection to database 'IdentityServer4Db' on server 'localhost\SQLEXPRESS'.
dbug: IdentityServer4.EntityFramework.Stores.ResourceStore[0]
Found PssUserMgtApi.Read, PssUserMgtApi.Write API scopes in database
fail: IdentityServer4.Validation.TokenRequestValidator[0]
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx cannot request OpenID scopes in client credentials flow
fail: IdentityServer4.Validation.TokenRequestValidator[0]
{
"ClientId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"ClientName": "xxxxxxxxxxxxxxxxxxxxxxxxx",
"GrantType": "client_credentials",
"Scopes": "xxxxxxxxxx.Read xxxxxxxxxxxxx.Write",
"Raw": {
"grant_type": "client_credentials",
"response_type": "id_token",
"scope": "xxxxxxxxxxxx.Read xxxxxxxxxxxxx.Write",
"client_secret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"client_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 5292.2873ms 400 application/json
dbug: Microsoft.AspNetCore.Server.Kestrel[9]
Connection id "0HL51IVGKG792" completed keep alive response.
Since there is no user tagged in a client credential flow, normally, client credential is not intended to have a scope tagged to it, and many frameworks doesnt support it.
https://www.oauth.com/oauth2-servers/access-tokens/client-credentials/ says :
scope (optional) :
Your service can support different scopes for the client credentials grant. In practice, not many services actually support this.
Check whether your client credential details are correct or not. You can also find this simple step by step explanation to configure client credential flow using this link
If you have this problem, just remove the 'openid' scope for a given client in the database in ClientScopes.
Actually the question already contains the answer:
grant_type client_credentials
response_type id_token
scope WidgetApi.Read WidgetApi.Write
client_secret xxxxxxxxxxxxxxxxxxxxxx
client_id WidgetApiClientId
The request of client_credentials type should be processed at token endpoint and must not require id_token as the flow is non-interactive. The redundant parameter is breaking the flow.
I get this error with IdentityServer4 2.1.3, but not with IdentityServer4 2.3.2. It seems, from the GitHub issues for the project, that it was fixed in 2.3:
https://github.com/IdentityServer/IdentityServer4/issues/2295#issuecomment-405164127
Related
I have a .NET 5 API project documented with SwaggerGen for which I'm trying to use Swashbuckle as the documentation UI. My auth provider is Auth0, so I'm looking to have the docs generate a JWT bearer token by making a valid OAuth2 call to the Auth0 /oauth/token endpoint. The Authorize button is appearing on the generated page and produces a form that asks the user for the client_id and client_secret, but when I press the Authorize button it issues a POST request that is missing client_id and client_secret. Specifically, it goes to the correct endpoint (/oauth/token) but has no query string parameters and only grant_type: client_credentials in the POST body. I can see this in the Chrome developer tools. Somehow the UI is just completely disregarding the values I've typed into the client_id and client_secret form fields.
Is there a trick to making the auth request use the values from the form? Here is the relevant part of my SwaggerGen configuration:
options.AddSecurityDefinition("OAuth2", new OpenApiSecurityScheme {
Type = SecuritySchemeType.OAuth2,
Name = "Bearer",
Description = "Authorization using the OAuth2 access token authorization flow",
Scheme = "Bearer",
In = ParameterLocation.Header,
Flows = new OpenApiOAuthFlows {
ClientCredentials = new OpenApiOAuthFlow {
TokenUrl = new Uri($"https://{_configuration["Auth0:HostedDomain"]}/oauth/token"),
AuthorizationUrl = new Uri($"https://{_configuration["Auth0:HostedDomain"]}/authorize")
}
}
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement {
{
new OpenApiSecurityScheme {
Reference = new OpenApiReference {
Type = ReferenceType.SecurityScheme,
Id = "OAuth2"
}
},
new List<string>()
}
});
Are you sure the Swagger UI does not send them, i.e. in the authorization header?
I had a similar problem because our OpenID server recognizes only client credentials (client_id, client_secret) sent in the body form and we have to select the correct "Client credentials location" option in the authorization dialog (Request body):
Then the client_id is sent in the request body correctly:
Received access token from AAD, using below url
https://login.microsoftonline.com/gdfdddddd-87dd-497c-b894-xxxxxx/oauth2/v2.0/token
grant_type :client_credentials
client_id :xxxxx-1ff5-4615-8d71-yyyyyy
client_secret:[7aCw]fdsfsfsfds.AC61Fg:cm33
scope : https://vault.azure.net/.default
Validated the above received token using below code manually & it works fine
IConfigurationManager<OpenIdConnectConfiguration> configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>("https://login.microsoftonline.com/TestDomain310320.onmicrosoft.com/v2.0/.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration openIdConfig = AsyncHelper.RunSync(async () => await configurationManager.GetConfigurationAsync(CancellationToken.None));
TokenValidationParameters validationParameters =
new TokenValidationParameters
{
ValidIssuer = "https://sts.windows.net/a3d2bff3-87dd-497c-b894-f63befdd7496/",
ValidAudiences = new[] { "https://vault.azure.net" },
IssuerSigningKeys = openIdConfig.SigningKeys
};
SecurityToken validatedToken;
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
var user = handler.ValidateToken(token, validationParameters, out validatedToken);
Modified parameter Scope:https://graph.microsoft.com/.default & received AAD token successfully but token validation using above code fails with error message "IDX10511: Signature validation failed. Keys tried: '[PII is hidden]'." Verified AAD app with above mentioned client id is having "user.read/user.read.basicall permissions". Why token validation fails if tokens are received from AAD with Scope:https://graph.microsoft.com/.default
Observation:
Token received with scope : https://vault.azure.net/.default
{
"typ": "JWT",
"alg": "RS256",
"x5t": "YMELHT0gvb0mxoSDoYfomjqfjYU",
"kid": "YMELHT0gvb0mxoSDoYfomjqfjYU"
}
While token received with Scope:https://graph.microsoft.com/.default has extra nonce property to avoid replay attack, is it be the reason for token validation failure?
{
"type": "JWT",
"nonce": "wCXLm9rF5Nma2S7OswU44uAVRpVbM_20WrWJkqbWe6Y",
"alg": "RS256",
"x5t": "YMELHT0gvb0mxoSDoYfomjqfjYU",
"kid": "YMELHT0gvb0mxoSDoYfomjqfjYU"
}
please suggest.
You should not be looking into, or validating tokens that were not issued to your own Apis. The intended receiver, KeyVault and MS Graph will do the necessary validation themselves. You should treat these Access Tokens as an opaque blobs that you stuff into the Authorization header in your calls to these Apis.
An Api owner, Graph or KeyVault can tomorrow can change the claims present in them or even choose to encrypt their tokens and your code will break.
Why are you validating tokens? If you are reading validated tokens of Apis that do not belong to you in your applications as a proof of Authentication, then you are setting yourself up for failure. Also its a security concern as any app in the world which can obtain an Access token for KeyVault or MS graph can pass it your Apis and compromise it.
Here is a discussion for reference - Cannot validate signature. #609
Yes, the error was caused by the nonce field in JWT header.
As far as I know, if we request the access token of graph api, the JWT token will contain the nonce field. And then we can't validate it on our backend(For security reasons, microsoft doesn't allow us to do this operation).
I have a REST API service provider, written in PHP. I tested it successfully in Postman, and it works properly there.
Now I am going to prepare API documentation for it, and I am using Swagger UI 3. I set it up properly and I can process Authorization with the top Authorize button.
After a successful login, I expect the respective Bearer token being set and used by the endpoints. But this is not gonna happen, when I try any endpoint, the REST server complains about lack of Authorization Header. I tested the network traffic, and there is no token along with the HTTP request.
My question is, how can I send the Bearer token in the header in Swagger UI, after successfully login using the Authorize button on the top? Is there any steps/process I should take to accompany the endpoint request with the token?
I used it in a .NET core project, and in the Startup file I had to put the following code part:
services.AddSwaggerGen(options =>
{
//authentication
var security = new Dictionary<string, IEnumerable<string>>
{
{"Bearer", new string[] { }},
};
options.AddSecurityDefinition("Bearer", new ApiKeyScheme
{
In = "Header",
Description = "Please insert JWT into field",
Name = "Authorization",
Type = "apiKey"
});
options.AddSecurityRequirement(security);
}
I have a .NET Web API and a small vanilla-JS app using ADAL.js, and I've managed to make them talk nicely to each-other and authenticate correctly.
If I console.log the token returned from adalAuthContext.acquireToken() and manually enter it as Authorization: Bearer {{token}} in Postman, I can also get a valid, authenticated, response from my backend.
However, I can't figure out how to configure Postman's built-in OAuth2.0 authentication UI to get me tokens automatically. I have managed to get tokens in several ways, but none of them are accepted by the backend.
How do I configure Postman to get a token the same way the ADAL.js library does?
For completeness, here's some code:
Backend configuration:
public void Configuration(IAppBuilder app)
{
app.UseCors(CorsOptions.AllowAll);
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
TokenValidationParameters = new TokenValidationParameters { ValidAudience = "<app-id>" },
Tenant = "<tenant>",
AuthenticationType = "WebAPI"
});
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
app.UseWebApi(config);
}
ADAL.js configuration:
const backendUrl = 'http://localhost:55476';
const backendAppId = '<app-id>';
const authContext = new AuthenticationContext({
clientId: backendAppId,
tenant: '<tenant>',
endpoints: [{ [backendAppId]: backendAppId }],
cacheLocation: 'localStorage'
});
Actually making a request:
authContext.acquireToken(backendAppId, (error, token) => {
// error handling etc omitted
fetch(backendUrl, { headers: { Authorization: `Bearer ${token}` } })
.then(response => response.json())
.then(console.log)
})
So since the Azure AD v1 endpoint is not fully standards-compliant, we have to do things in a slightly weird way.
In Postman:
Select OAuth 2.0 under Authorization
Click Get new access token
Select Implicit for Grant Type
Enter your app's reply URL as the Callback URL
Enter an authorization URL similar to this: https://login.microsoftonline.com/yourtenant.onmicrosoft.com/oauth2/authorize?resource=https%3A%2F%2Fgraph.microsoft.com
Enter your app's application id/client id as the Client Id
Leave the Scope and State empty
Click Request token
If you configured it correctly, you'll get a token and Postman will configure the authorization header for you.
Now about that authorization URL.
Make sure you specify either your AAD tenant id or a verified domain name instead of yourtenant.onmicrosoft.com.
Or you can use common if your app is multi-tenant.
The resource is the most important parameter (and non-standards-compliant).
It tells AAD what API you want an access token for.
In this case I requested a token for MS Graph API, which has a resource URI of https://graph.microsoft.com.
For your own APIs, you can use either their client id or App ID URI.
Here is a screenshot of my settings:
I am trying to access GoogleDrive API (my own a/c) using the refresh token --issued from OAuth Playground -- like below. I am using my refresh token and access token for offline access but I am getting a 401 unauthorized. My code is based on reference belwoand google-api javadocs recommendation for building an offline request
I do not wish to use a browser, i wish to run this from a server side app to upload to my own a/c.
ref: http://stackoverflow.com/questions/10533203/fetching-access-token-from-refresh-token-using-java
Code:
public static void main(String[] args) throws IOException {
HttpTransport TRANSPORT = new NetHttpTransport();
JsonFactory JSON_FACTORY = new JacksonFactory();
String refreshTokenString="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; //from console
String accessTokenString ="yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"; //from console
GoogleCredential credential = createCredentialWithRefreshToken(
TRANSPORT, JSON_FACTORY, new TokenResponse().setRefreshToken(refreshTokenString));
credential.setAccessToken("accessTokenString");
//exeucute HTTP request for offline accessTokenString
// Execute HTTP GET request to revoke current token.
HttpResponse response = TRANSPORT.createRequestFactory()
.buildGetRequest(new GenericUrl(
String.format(
"https://www.googleapis.com/drive/v2/changes",
credential.getAccessToken()))).execute();
System.out.println("RESPONSE: " +response);
}
public static GoogleCredential createCredentialWithRefreshToken(HttpTransport transport,
JsonFactory jsonFactory, TokenResponse tokenResponse) {
HttpTransport TRANSPORT = new NetHttpTransport();
JsonFactory JSON_FACTORY = new JacksonFactory();
return new GoogleCredential.Builder().setTransport(transport)
.setJsonFactory(JSON_FACTORY)
.setClientSecrets(CLIENT_ID, CLIENT_SECRET)
.build()
.setFromTokenResponse(tokenResponse);
}
And the error:
{
"error": {
"errors": [
{
"domain": "global",
"reason": "required",
"message": "Login Required",
"locationType": "header",
"location": "Authorization"
}
],
"code": 401,
"message": "Login Required"
}
}
I am using a valid refresh token and access token (works from playground console) but i get login required 401 here when running from Java app. I requires this for daily backups from server directly to Drive.
You can't use the access or refresh token from the Playgorund in your application. These tokens where issued by google only to be used in the playground. The tokens you get are tied to a Client Id and Client Secret. Obviously your CLIENT_ID and CLIENT_SECRET are different from the ones which are used by the Google Playground application.
You really need your application to request these tokens, so for the first time you need a browser, so that the user can accept that YOUR app (not Google Playground) is getting your information stored at google. This is called "user consent", and shown in the little diagram at this page: Using OAuth 2.0 for Web Server Applications)
Then, when you have the refresh token for your application. You don't need the browser any more for your daily backups.
You can use your code with a valid refresh token for your app, as presented in your post.
Can I access using the refresh token only as this does not expire?
No. You must convert the refresh token to an access token.