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.
Related
we have an issue with API calls. As the subject says, we cannot access the backend API with credentials.
Our customer's production and staging instances run Shopware 6.2.3 and we program plugins for Shopware. So now we want to update the customer's instances to 6.4.x.x. We also have a freshly instaled dev instance (6.4.13.0) which we use for testing our own plugins for SW 6.4.
Now we are facing an issue we cannot explain. We took a copy of the production and updated it from 6.2.3 to 6.4.13.0 which worked without any major issues. But our API call always fail with this error:
{"errors":[{"code":"6","status":"400","title":"The user credentials were incorrect.","detail":null}]}
The credentials are definitely correct, we can use them for logging into the backend.
The same error occurs when we use Curl with the same payload. On our 6.4 dev instance (see above) the Curl command and our plugin both work flawlessly and are able to get an access token.
This is the Curl command we used for testing:
curl --request POST --url https://our-domain.example/api/oauth/token --header 'Authorization: ' --header 'Content-Type: application/json' --data '{"grant_type": "password", "username": "xxxxx", "password": "xxxxxxxx", "client_id": "administration"}'
This is the code our developer wrote; it works perfectly on the 6.4 dev instance:
private function _getToken($domain, $username, $pass)
{
if ($domain[strlen($domain)-1] !== "/")
{
$domain .= "/";
}
$endpoint = $domain . "api/oauth/token";
$config = [];
if (strpos($endpoint, "https") > -1) {
$config = ['verify' => false];
}
$client = new Client($config);
$json_encode = json_encode([
'username' => $username,
'password' => $pass,
'grant_type' => 'password',
"client_id" => "administration",
]);
$response = $client->request('POST', $endpoint, [
'body' => $json_encode,
"headers" => ["Content-Type" => "application/json"]
]);
return json_decode($response->getBody())->access_token;
}
To rule out there was a problem with the Shopware update to 6.4, we tested the API call on the untouched production and staging instances and voilà: same error, but with a small difference. It throws an error 401 except 400.
So something is obviously wrong with the 6.2.3 instances.
Any idea what we can check? Is there anything inside the Shopware core or database that prevents API authentication?
Any help is greatly appreciated! Thanks in advance!
Nevermind, we found out what the problem was. Curl had an issue with verifying the (still valid) SSL certificate. So we tried to renew it and Let'sEncrypt failed because the web server was lacking an IPv6 address. So we added it to the virtual host, renewed the certificate and now the API call gives us the token.
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);
}
My firebase cloud function contains protected routes that can only be accessed when passed a valid IdToken in the request header. cloud functions API looks like this
functions/index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp({
credential: admin.credential.applicationDefault(),
databaseURL: "DB_URL"
});
const express = require('express');
const app = express();
const authenticate = async (req, res, next) => {
if (!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) {
res.status(403).send('Unauthorized');
return;
}
const idToken = req.headers.authorization.split('Bearer ')[1];
try {
const decodedIdToken = await admin.auth().verifyIdToken(idToken);
req.user = decodedIdToken;
next();
return;
} catch(e) {
res.status(403).send('Unauthorized');
return;
}
};
app.use(authenticate);
app.get('/protected', async (req, res) => {
return res.send('OK');
});
exports.api = functions.https.onRequest(app);
Initially, I was using the Firebase Authentication to create new user and to get IdToken
Creating new user
curl 'https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=[API_KEY]' \
-H 'Content-Type: application/json' \
--data-binary '{"email":"[user#example.com]","password":"[PASSWORD]","returnSecureToken":true}'
Getting IdToken so I can pass it to the firebase cloud functions to access the protected routes
curl 'https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=[API_KEY]' \
-H 'Content-Type: application/json' \
--data-binary '{"email":"[user#example.com]","password":"[PASSWORD]","returnSecureToken":true}'
calling my protected cloud functions using this approach is working fine
curl --location --request GET 'http://localhost:5001/abc-production/us-central1/api/protected/' \
--header 'Authorization: Bearer SECRET_ID_TOKEN'
Now, Instead of using the Firebase Authentication, I'd like to use the Authentication Emulator to create new users and generate IdToken's.
I can create new users using the Auth emulator UI, but how do I generate the access token of those users? Are there any API endpoints that can return the IdToken of the locally saved user so that I can test my protected API without adding users in production?
Also, When I run the auth emulator, IdToken's generated using the production Firebase Authentication environment doesn't work.
"code": "auth/argument-error",
"message": "Firebase ID token has invalid signature. See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token."
I just answered a very similar question here. The answer is the same so sorry if I'm duplicating...
Basically, to authenticate a user you can make the following HTTP POST request (assuming your Authentication emulator runs on port 9099):
http://localhost:9099/identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=any_key_you_want
Body (JSON):
{
"email": "your-user#mail.com",
"password": "some-password"
}
The emulator is intended only for local testing and not for production and you cannot use the IdToken issued by the emulator to access other services like Firebase Functions because and as the documentation says:
For security reasons, the Authentication emulator issues unsigned ID
Tokens which are only accepted verifyIdToken and createSessionCookie
of the Firebase Admin SDK when running inside the Cloud Functions
emulator.
So, these token are only valid in the emulator environment and if you want to use them with Firebase Function, you can do that only with the Functions emulator. check this guide on how to run the functions locally.
I've set up a simple, standard environment Google App Engine project which uses Cloud Endpoints by going through the steps in the tutorial here:
https://cloud.google.com/endpoints/docs/frameworks/python/get-started-frameworks-python
This works great - I can make a curl call to the echo endpoint and get the expected result.
However, I can't successfully call the authenticated endpoint.
I'm following the steps here: https://cloud.google.com/endpoints/docs/frameworks/python/javascript-client and, while I can successfully sign in, when I send my sample authenticated request I get a 401 Unauthorized HTTP response.
From the log on the server I see :
Client ID is not allowed: <my client id>.apps.googleusercontent.com (/base/data/home/apps/m~bfg-data-analytics/20190106t144214.415219868228932029/lib/endpoints/users_id_token.py:508)
So far I've checked:
The web app is using the correct version of the cloud endpoints config.
The client ID in the endpoint config (x-google-audiences) matches the
client ID that the javascript web app is posting.
Any ideas on how to fix this?
Using the example code to set up the end point in:
https://cloud.google.com/endpoints/docs/frameworks/python/create_api
and
https://cloud.google.com/endpoints/docs/frameworks/python/service-account-authentication
And modifying the python code for generating a token from :
https://github.com/GoogleCloudPlatform/python-docs-samples/tree/master/endpoints/getting-started/clients/service_to_service_google_id_token
I've got it working.
Here's the server endpoint code:
import endpoints
from endpoints import message_types
from endpoints import messages
from endpoints import remote
class EchoRequest(messages.Message):
message = messages.StringField(1)
class EchoResponse(messages.Message):
"""A proto Message that contains a simple string field."""
message = messages.StringField(1)
ECHO_RESOURCE = endpoints.ResourceContainer(
EchoRequest,
n=messages.IntegerField(2, default=1))
#endpoints.api(
name='echo',
version='v1',
issuers={'serviceAccount': endpoints.Issuer(
'MY-PROJECT#appspot.gserviceaccount.com',
'https://www.googleapis.com/robot/v1/metadata/x509/MY-PROJECT#appspot.gserviceaccount.com')},
audiences={'serviceAccount': ['MY-PROJECT#appspot.gserviceaccount.com']})
class EchoApi(remote.Service):
# Authenticated POST API
# curl -H "Authorization: Bearer $token --request POST --header "Content-Type: applicationjson" --data '{"message":"echo"}' https://MY-PROJECT#appspot.com/_ah/api/echo/v1/echo?n=5
#endpoints.method(
# This method takes a ResourceContainer defined above.
ECHO_RESOURCE,
# This method returns an Echo message.
EchoResponse,
path='echo',
http_method='POST',
name='echo')
def echo(self, request):
print "getting current user"
user = endpoints.get_current_user()
print user
# if user == none return 401 unauthorized
if not user:
raise endpoints.UnauthorizedException
# Create an output message including the user's email
output_message = ' '.join([request.message] * request.n) + ' ' + user.email()
return EchoResponse(message=output_message)
api = endpoints.api_server([EchoApi])
And the code to generate a valid token
import base64
import json
import time
import google
import google.auth
from google.auth import jwt
def generate_token(audience, json_keyfile, client_id, service_account_email):
signer = google.auth.crypt.RSASigner.from_service_account_file(json_keyfile)
now = int(time.time())
expires = now + 3600 # One hour in seconds
payload = {
'iat': now,
'exp': expires,
'aud' : audience,
'iss': service_account_email,
'sub': client_id,
'email' : service_account_email
}
jwt = google.auth.jwt.encode(signer, payload)
return jwt
token = generate_token(
audience="MY-PROJECT#appspot.gserviceaccount.com", # must match x-google-audiences
json_keyfile="./key-file-for-service-account.json",
client_id="xxxxxxxxxxxxxxxxxxxxx", # client_id from key file
service_account_email="MY-PROJECT#appspot.gserviceaccount.com")
print token
Make an authenticated call with curl
export token=`python main.py`
curl -H "Authorization: Bearer $token" --request POST --header "Content-Type: application/json" --data '{"message":"secure"}' https://MY-PROJECT.appspot.com/_ah/api/echo/v1/echo?n=5
I'm trying to access the Taxee.io API using the request npm module. The documentation is slightly poor and the difference between the Mashape info and the website's info is confusing.
https://taxee.io/
The docs have one example of a request here.
curl 'https://taxee.io/api/v2/calculate/2017' -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJBUElfS0VZX01BTkFHRVIiLCJodHRwOi8vdGF4ZWUuaW8vdXNlcl9pZCI6IjU4NDQ4MTA4Mzg2NjhhMTU4ZDU0ZmIzNSIsImh0dHA6Ly90YXhlZS5pby9zY29wZXMiOlsiYXBpIl0sImlhdCI6MTQ5OTA1MzU0NX0.pOwC5JEC7trLaaZVgHHGu_rvN0-EGa3RMm8BgJ-M9gk' -H 'Content-Type: application/x-www-form-urlencoded' --data 'state=NC&filing_status=married&pay_periods=26&pay_rate=116500&exemptions=2'
I however want to use the request npm module and am struggling to bridge the gap in how it will work in my express app.
const request = require('request');
request.post('https://taxee.io/api/v2/calculate/2017', {
'auth': {
'Bearer': 'mykey'
}
});
This is what I have thus far. Any help is appreciated.
Keep in mind that properties are case sensitive in JavaScript. You must pass the bearer token under the key bearer and not Bearer.
To replicate the Content-type and pass data, use the form support of the library.
E.g. like this:
{
auth: {
bearer: '<token>',
},
form: {
state: 'NC',
// ...
},
}