I am trying to create a new resource in keycloak
on keycloak UI I looged in with admin account then I created a realm called demo and a user abc in it and created a client clientA after that I have created a new resource named resourceA with scopes and policy and permissions
while on keycloak ui everything is working fine resource was created the user abc has realm roles as admin, uma_authorization and client role of the client clienA is uma_protection
when i try to create a resource via rest api i am getting {"error":"unknown_error"}
Here are the steps I am following
obtained a pat as shown here
curl -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d 'grant_type=client_credentials&client_id=clientA&client_secret=given client secret' \
"http://localhost:8080/auth/realms/demo/protocol/openid-connect/token"
I got the access token (pat) then I followed next step
curl -v -X POST \
http://localhost:8080/auth/realms/demo/authz/protection/resource_set \
-H 'Authorization: Bearer '$here i am adding the pat i received from the previous step \
-H 'Content-Type: application/json' \
-d '{
"resource_scopes":[
"read-public",
"post-updates",
"read-private",
"http://www.example.com/scopes/all"
],
"icon_uri":"http://www.example.com/icons/sharesocial.png",
"name":"Tweedl Social Service",
"type":"http://www.example.com/rsrcs/socialstream/140-compatible"
}'
I got {"error":"unknown_error"} as a response with status code 400 Bad request. What am I missing here ?
I know what it feels like :)
Reason
org.keycloak.services.error.KeycloakErrorHandler gets error text from WebApplicationException::getMessage method result.
public Response toResponse(Throwable throwable) {
//...
error.setError(getErrorCode(throwable));
//...
}
private String getErrorCode(Throwable throwable) {
if (throwable instanceof WebApplicationException && throwable.getMessage() != null) {
return throwable.getMessage();
}
return "unknown_error";
}
And if caused exception is another type of RuntimeException you will get "unknown_error"
Solution (short path):
Extend WebApplicationException and implement your own constructor and getMessage method like that:
public class CustomException extends WebApplicationException {
private final String message;
public AbstractUserException(Throwable throwable) {
// log exception if you want
this.message = throwable.getMessage();
}
#Override
public String getMessage() {
return message;
}
}
Wrap the main code of your resource in the try catch block, which always will throw your custom Exception
try {
// your code
} catch (Exception e) {
throw new CustomException(e);
}
Voila! You will get a readable error in response
P.S. If you want to go further - you can throw your exception with a complete HTTP Response object, which may contain custom HTTP status code, etc
Related
In the UPS developer portal, I have created an application that has a Client Id and a Client Secret. Next, I want to obtain an OAuth token so I can use it to access their other APIs. I am creating my token request as per the spec and I am receiving the following error:
{"response":{"errors":[{"code":"10400","message":"Invalid/Missing Authorization Header"}]}}
The spec has a "try it out" feature where you can obtain a test token. It prompts the user to fill in a x-merchant-id parameter and a grant_type form variable and creates a curl request that looks like this:
curl -X POST "https://wwwcie.ups.com/security/v1/oauth/token"
-H "accept: application/json"
-H "x-merchant-id: {My_Client_Id_Goes_Here}"
-H "Content-Type: application/x-www-form-urlencoded"
-d "grant_type=client_credentials"
For x-merchant_id, I have used my app’s Client Id. It is not clear if the value for grant_type should be the phrase client_credentials (the page makes it seem like this is the only valid value) or my app’s actual Client Secret. I have tried both and get the same error each time.
There are a million examples out there on how to use their (old style) API keys, but practically nothing about how to obtain an OAuth token except for the instructions linked above!
Your curl looks good to me, just missing the Authorization header which is a base64(id:secret)
curl -X POST "https://wwwcie.ups.com/security/v1/oauth/token"
-H "Authorization: Basic {id}:{secret}"
-H "accept: application/json"
-H "x-merchant-id: {My_Client_Id_Goes_Here}"
-H "Content-Type: application/x-www-form-urlencoded"
-d "grant_type=client_credentials"
If you're using the 'Try out' feature, select the Authorize button at the top and enter the client id and secret, that's where its used to set the Authorization header. One thing to note, the 'Try out' feature only work with the Test product(s) assigned to your app
Additional info
UPS have 2 environments
Testing: wwwcie.ups.com
Production: onlinetools.ups.com
Testing env only accepts Test Products, so note the product(s) that was added to your app
I was stuck with this issue for a long time.
Your comments did eventually help me. But I wanted to make it more clear for someone else reading this later....
Instead of using UPS username and password in the authorization header. You need to encode the clientId and secret with a colon between and send that.
For PHP:
$clientID = base64_encode("{clientID}:{clientSecret}");
$headers = array();
$headers[] = "Authorization: Basic $clientID";
$headers[] = 'Accept: application/json';
$headers[] = "X-Merchant-Id: {clientID}";
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
One more addition to the other answers: make sure you add the "OAuth" product to your UPS app. I had added "tracking" and "tracking test", but not OAuth. I was getting the "{"code":"10401","message":"ClientId is Invalid"}" response when I tried to get a token, even though I was sure I had everything else right.
Adding OAuth to my UPS app presumably added my ClientID to their OAuth system, and my token requests started working.
Just in case somebody with .NET/C# background will be looking for the similar topic - an UPS RESTFul API authorization and tracking info processing solution here is the one working well for me using proposed here approach:
#define TEST_MODE
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
var myClientID = "{Type your ClientId here}";
var mySecretID = "{Type your SecretID here}";
#if TEST_MODE
var baseAddress = "https://wwwcie.ups.com"; // testing
#else
var baseAddress = "https://onlinetools.ups.com"; // production
#endif
var accessID = $"{myClientID}:{mySecretID}";
var base64AccessID = Convert.ToBase64String(Encoding.ASCII.GetBytes(accessID));
using (var client = new HttpClient())
{
// Get Access Token
var request = new HttpRequestMessage()
{
Method = HttpMethod.Post,
RequestUri = new Uri($"{baseAddress}/security/v1/oauth/token"),
Content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", "client_credentials")
})
};
request.Headers.Add("Authorization", $"Basic {base64AccessID}");
var response = await client.SendAsync(request);
var jsonResult = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<JsonObject>(jsonResult);
var access_token = result?["access_token"]?.ToString();
// Get Tracking Info
var trackingNumber = "1Z5338FF0107231059"; // provided by UPS for testing
request = new HttpRequestMessage()
{
Method = HttpMethod.Get,
RequestUri = new Uri($"{baseAddress}/api/track/v1/details/{trackingNumber}")
};
request.Headers.Add("Authorization", $"Bearer {access_token}");
request.Headers.Add("transId", $"{DateTime.Now.Ticks}");
#if TEST_MODE
request.Headers.Add("transactionSrc", $"testing");
#else
request.Headers.Add("transactionSrc", $"{App Name and version}");
#endif
response = await client.SendAsync(request);
jsonResult = await response.Content.ReadAsStringAsync();
Console.WriteLine(jsonResult);
}
Using Strava API v3 and create activity method, I'd like to mute activity. So, according to the docs, I should set hide_from_home to true.
However, the flag doesn't work correctly. Meaning, I'm receiving response containing:
"hide_from_home": false
And, activity is visible in Active Feed.
I tried to send the request via Flurl (C#) and curl (Postman).
The Flurl code looks more like a:
var rs = await host.AppendPathSegments("activities")
.WithOAuthBearerToken(accessToken)
.AllowAnyHttpStatus()
.PostUrlEncodedAsync(new Dictionary<string, dynamic>
{
{ "name", "😎 test from api" },
{ "type", "Walk" }, // https://developers.strava.com/docs/reference/#api-models-ActivityType
{ "sport_type", "Walk" }, // https://developers.strava.com/docs/reference/#api-models-SportType
{ "start_date_local", DateTime.Now.AddMilliseconds(-1).ToString("s", CultureInfo.InvariantCulture) },
{ "elapsed_time", 2 }, // In seconds
{ "hide_from_home", true },
})
.ReceiveString();
I tried to use dynamic, object and string as a dictionary value type and tried to pass true (as a boolean) and "true" (as a string), but none of these worked.
For curl, I imported example from the Strava Playground (Swagger UI) [Authorization removed in that example]:
curl -X POST "https://www.strava.com/api/v3/activities" -H "accept: application/json" -H "Content-Type: application/x-www-form-urlencoded" -d "name=Api&type=Walk&sport_type=Walk&start_date_local=2022-08-12&elapsed_time=2&hide_from_home=true"
So, the question: How to create muted activity via Strava API?
When implementing Basic Authentication on Ktor and configuring a Provider, which validates whether the credentials are legit by returning a non null Principal, like in this example:
install(Authentication) {
basic("auth-basic") {
realm = "Access to the '/' path"
validate { credentials ->
if (credentials.name == "fernando" && credentials.password == "foobar") {
UserIdPrincipal(credentials.name)
} else {
null
}
}
}
}
If the credentials are invalid and a null is returned, then Ktor automatically communicates with the client by triggering a 401 - Unauthorized, which in terms of behavior is what is expected...
But I cannot provide/add any extra information, like for example where exactly the issue was: username or password.
Any idea on how to mitigate this?
for resolve this problem you can use StatusPages by install it on application calss.
like below:
install(StatusPages) {
status(HttpStatusCode.Unauthorized) {
call.respond(HttpStatusCode.Unauthorized, "Your Response Object")
}
}
for more informatin please read these links:
https://ktor.io/docs/status-pages.html
https://github.com/ktorio/ktor/issues/366
The message when the token expires can be shown using StatusPages or by using the challenge method in the JWTAuth class like this:
jwt {
challenge { _, _ ->
call.respond(HttpStatusCode.Unauthorized, "Token is not valid or has expired")
}
}
This is regarding to my project, Where I am write Poco SSL client to communicate with a server.
I am able to do (i) Basic Auth (ii) Cert exchange. But after sending post request I am facing "Error 500".
Let me explain it=>
I have a working curl:
curl -d '{"name":"com.my.session.value"}' -H 'Content-Type: application/json' -H 'Accept: application/json' -E MyCert.pem --key MyKey.pem -u testuser -k -vvv https://<server-ip>:<port>/Internal/directory/path
using this I am able to print data on console. So tried to write same in Poco/C++:
// I have handler for keyPassparse, cert, invalid-cert-handler and used them in below line
SSLManager::instance().initializeClient(parse_str, Cert, ptrContext);
URI uri(argv[1]);
Poco::Net::HTTPSClientSession session(uri.getHost(), uri.getPort());
session.setKeepAlive(true);
Poco::Net::HTTPRequest req(Poco::Net::HTTPRequest::HTTP_POST, uri.getPath(), Poco::Net::HTTPMessage::HTTP_1_1);
HTTPBasicCredentials cred("testuser", "secret");
//Here I tried to add headers and data
cred.authenticate(req);
req.add("Content-Type","application/json");
req.add("Accept","application/json");
req.add("data","com.my.session.value"); // try-1 to add data
req.setKeepAlive(true);
std::ostream& myOStream = session.sendRequest(req);
std::string body("name=com.my.session.value"); // try-2 to add data
myOStream << body;
Poco::Net::HTTPResponse response;
std::istream& rs = session.receiveResponse(response);
std::cout << response.getStatus() << " " << response.getReason() << std::endl;
}
catch (Exception& exc)
{
std::cerr << exc.displayText() << std::endl;
return 1;
}
return 0;
}
This is always returning Error:500 (Internal server error)
Which means my data section is not reaching properly.
Please suggest me a way to pass proper "data" section to server.
Thanks in advance.
I found the solution for this:
1) I sent data as json:
Poco::JSON::Object obj;
obj.set("name", "com.my.session.value");
std::stringstream ss;
obj.stringify(ss);
2) Content herders should not be added by "add", used below for them:
req.setContentType("application/json");
req.setContentLength(ss.str().size());
3) Now sending body like this:
std::ostream& myOStream = session.sendRequest(req);
obj.stringify(myOStream);
Approach used:
I wrote code for http.
Sent same data by curl and exe, captured packets for both.
Compared and fixed gaps one by one.
I hope this will help someone in future.
Beginner level query alert. IdentityServer4 Tutorial After going through the tutorials what I inferred was that-
I create an authorization server, whose job is to issue token for the client with proper authentication.
My Authorization Server runs first, and includes information and definitions of the API and client.
The API has an authentication middleware that validates the incoming token to make sure if its coming from a trusted source and also its scope.
The client requests a token from the authorization server and then sends request to the API with the token received.
For all this, I had to run the authorization server first, the API next and then the Client. My requirement is that I don't need a start and stop server which runs separately to take care of authentication. I have one API and I need it to double as the authorization server too. Is this possible? Is it possible for the API to generate tokens, validate them and then tend to the requests, all the while using IdentityServer4.
Update Jan 2020: For a ASP.NET Core 3.1 example of using IdentityServer4 in the same project as ASP.NET Core API controllers, you can have a look at my IdentityServer4 with MVC Controllers and AppInsights sample repo. It's goal was to test AppInsights, but it does demonstrate a SPA stub that calls both OpenID endpoints (⚠ in a non-recommended wa, using client credentials), and controller endpoints.
Although typically the Auth Server will be separate from the Resource Server, this doesn't need to be the case. You can just add all of it to one application. Here's an example.
Create a new ASP.NET Core (I used 2.0) Web API application.
Install-Package IdentityServer4 -Version 2.0.0-rc1 (at the time of writing rc1 is the version with .NET Core 2.x support)
Install-Package Microsoft.AspNetCore.Authentication.JwtBearer
Set [Authorize] on ValuesController from the template
Add this code to Configure(...) in class Startup above app.UseMvc():
// calls app.UseAuthentication() for us
// See: http://docs.identityserver.io/en/release/quickstarts/6_aspnet_identity.html
app.UseIdentityServer();
Add this code to ConfigureServices(...) in class Startup:
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiResources(new[]
{
new ApiResource
{
Name = "MyApi",
ApiSecrets = { new Secret("supersecret".Sha256()) },
Scopes = { new Scope("myapi") },
}
})
.AddInMemoryClients(new[]
{
new Client
{
ClientId = "api",
ClientSecrets = { new Secret("supersecret".Sha256()) },
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
AllowedScopes = { "myapi" },
}
})
.AddTestUsers(new List<TestUser>
{
new TestUser
{
SubjectId = "some-unique-id-12345678980",
Username = "john",
Password = "123456"
}
});
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opts =>
{
opts.Authority = "http://localhost:51689";
opts.Audience = "MyApi";
opts.RequireHttpsMetadata = !env.IsDevelopment();
});
If you now F5 the app it will show an empty page because of a "401 Unauthorized" response. You can also now check this endpoint: http://localhost:51689/.well-known/openid-configuration (with your dev port of course).
You can also do this now:
curl -X POST \
http://localhost:51689/connect/token \
-H 'authorization: Basic YXBpY2xpZW50aWQ6c3VwZXJzZWNyZXQ=' \
-H 'cache-control: no-cache' \
-H 'content-type: application/x-www-form-urlencoded' \
-d 'username=john&password=123456&grant_type=password'
Note that the authorization header contains a base64 encoded string representing the string "apiclientid:supersecret". This should give you a result like this:
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjczODhkMjY0MDg4Y2NjOGRiZTcwODIzZGIxYzY3ZWNkIiwidHlwIjoiSldUIn0.eyJuYmYiOjE1MDUwODE3OTAsImV4cCI6MTUwNTA4NTM5MCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MTY4OSIsImF1ZCI6WyJodHRwOi8vbG9jYWxob3N0OjUxNjg5L3Jlc291cmNlcyIsIk15QXBpIl0sImNsaWVudF9pZCI6ImFwaWNsaWVudGlkIiwic3ViIjoic29tZS11bmlxdWUtaWQtMTIzNDU2Nzg5ODAiLCJhdXRoX3RpbWUiOjE1MDUwODE3OTAsImlkcCI6ImxvY2FsIiwic2NvcGUiOlsibXlhcGkiXSwiYW1yIjpbInB3ZCJdfQ.sxWodlJKDJgjoOj-8njZ8kONOqiKgj3E5YlKXGX5cz-WqUK7RHKJacNX09D00Y8YtmZpkc5OrY0xzOx7UuSAtDku4oOX_1o38XEGJPBSJHdjqgVGSOU-hwDkzin8HSRJ0Kna1vM3ZzTh80cFTVhP8h903GAPRrAyV8PtRXnwV0CPel8NdvML6dV-mfDpGi0l7crp-TPnH4nIG0olpRYUPV5EsgCVMG9vswnOnKz3RPOGaU8yJy7_9mbQW5GHKfN0J6swiSt5rY3NKs_t1P9-tnCDKBOAafaXjLEO3Kx4fP4xTgwK92uKcEDDnRZo_-T0CkBxnSQm0oz1sUyrW8_3Pg",
"expires_in": 3600,
"token_type": "Bearer"
}
In addition to the option of switching to other authentication flows, you can also add a controller method like this:
[Route("api/token")]
public class TokenController
{
[HttpPost("request")]
public async Task<JObject> Request(string username, string password)
{
var tokenClient = new TokenClient("http://localhost:51689/connect/token", "apiclientid", "supersecret");
var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync(username, password);
if (tokenResponse.IsError) { /* Log failed login attempt! */ }
return tokenResponse.Json;
}
}
And then call it like this:
curl -X POST \
http://localhost:51689/api/token/request \
-H 'cache-control: no-cache' \
-H 'content-type: application/x-www-form-urlencoded' \
-d 'username=john&password=123456'
This should give a similar response as above.
You can now provide this access_token insde a header Authorization: Bearer access_token_should_go_here like this:
curl -X GET \
http://localhost:51689/api/values \
-H 'authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjczODhkMjY0MDg4Y2NjOGRiZTcwODIzZGIxYzY3ZWNkIiwidHlwIjoiSldUIn0.eyJuYmYiOjE1MDUwODIyODQsImV4cCI6MTUwNTA4NTg4NCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MTY4OSIsImF1ZCI6WyJodHRwOi8vbG9jYWxob3N0OjUxNjg5L3Jlc291cmNlcyIsIk15QXBpIl0sImNsaWVudF9pZCI6ImFwaWNsaWVudGlkIiwic3ViIjoic29tZS11bmlxdWUtaWQtMTIzNDU2Nzg5ODAiLCJhdXRoX3RpbWUiOjE1MDUwODIyODQsImlkcCI6ImxvY2FsIiwic2NvcGUiOlsibXlhcGkiXSwiYW1yIjpbInB3ZCJdfQ.hQ60zzEbZOSVpP54yGAnnzfVEks18YXn3gU2wfFgNB33UxQabk1l3xkaeUPTpuFdmFTm4TbVatPaziGqaxjzYgfdVoAwQ3rYJMuYzOh0kUowKxXTkquAlD13ScpvxrGeCXGxFTRHrxX2h-1hHGQ9j2y2f3-ESynzrCdxp5HEH1271BSYfQ7pZIzvyxxpbmOzzKDzdYfcJV6ocnOU4jXBhw6iOzqpR03zxxtjIjGbJd2QwWklBGqZlO_thdZZFi-t7zu5eC4wqRCYGGZYWOUC17_Btc_Irg2SsvLCUDzsaBw7AVgLpZ7YjF-RsVqIi6oxNQ2K0zllzUy8VbupbWKr5Q' \
-H 'cache-control: no-cache' \
And now you should get past the [Authorize] atribute. Yay!
You now have one web application, which acts as both an Auth Server and a Resource Server.
Fun fact: with the above example the AddJwtBearer options specify the application's own url as an Authority, making the app request from itself the public key to use for validating the tokens. You could instead also use code to directly provide this key to the authentication middleware.