OAuth : App development working with Microsoft Entra ID (Azure AD)
- Build Standard Web or Native Application
- Build JavaScript Application
- Build Backend Application (Daemon app, Service app)
- How to Verify Token
- Build Your Custom API Application
- Workload Identity Federation
You can now build your own Web API protected by the OAuth flow and you can add your own scopes with Entra ID (also with Azure AD B2C).
Here I show you how to setup, how to build, and how to consider custom scopes in Entra ID. (You can also learn several OAuth scenarios and ideas through this post.)
I note that : As I described in “How to verify id token in Entra ID“, the access token in consumer account (Microsoft Account) is not resolved in your own application. (It’s in black-box by Microsoft.) For this reason, you cannot build your custom API application in consumer accounts (Microsoft Account).
Please use organization accounts (Entra ID accounts) for realizing the following scenarios.
Note : For Azure AD B2C, please refer the post “Azure AD B2C Access Tokens now in public preview” in team blog.
Register your own Web API
Here we register our custom Web API in v2.0 endpoint, and consent this app in your tenant.
First, you login to Azure Portal and go to “Azure Active Directory” (Entra ID). In Entra ID management, click “App registrations” in the navigation, and then push “New registration” to register your API app. In app registration wizard, be sure to select an option “Accounts in any organizational directory (Any Azure AD directory – Multitenant) and personal Microsoft accounts (e.g. Skype, Xbox)” (i.e, which means v2 endpoint application).
In application page, click “Expose an API” in the navigation and add 2 scopes, “read
” and “write
” scopes. (We assume that each scopes are api://f6da5452-7f05-4182-bd2d-feac1d2e86e2/read
and api://f6da5452-7f05-4182-bd2d-feac1d2e86e2/write
.)
This scope is used as follows.
For example, when a client application (remote application) wants to read some data from your custom Web API, this client application should call the following OAuth request for permission, then the client application can get access token for calling your Web API with “read” scope.
Your custom Web API, in turn, should check this incoming access token and grant this application to access your resource.
https://login.microsoftonline.com/common/oauth2/v2.0/authorize ?response_type=id_token+code &response_mode=form_post &client_id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx &scope=openid+api%3a%2f%2ff6da5452-7f05-4182-bd2d-feac1d2e86e2%2fread &redirect_uri=https%3A%2F%2Flocalhost%2Ftest &nonce=abcdef
In the real scenario, your custom Web API will also have to provide user interface for giving permissions.
For example, please remember “Office 365”. The organizations or users who don’t purchase (subscribe) Office 365 cannot take the Office 365 API’s permissions. (No Office 365 API permissions are displayed in their Entra ID settings.) After you (or organization’s admin) purchase Office 365 in https://portal.office.com/, you can start to use these APIs.
Your custom API is the same. Before using these custom scopes, the user have to include this custom application in user’s tenant or an individual user.
When a user accesses the following url in web browser and login with the user’s credential, the following consent UI will be displayed. Once the user approves this consent, this custom Web API application is registered in user’s individual permissions. (Note that the client_id
is the application id of this custom Web API application, and the redirect_uri
is the redirect url of this custom Web API application. Please change these values to meet your application’s settings.)
https://login.microsoftonline.com/common/oauth2/v2.0/authorize ?response_type=id_token &response_mode=form_post &client_id=f6da5452-7f05-4182-bd2d-feac1d2e86e2 &scope=openid &redirect_uri=https%3A%2F%2Flocalhost%2Ftestapi &nonce=abcdef
Note : You can revoke the permission from :
https://account.activedirectory.windowsazure.com/, when you are using the organization account (Entra Account). https://account.live.com/consent/Manage, when you’re using the consumer account (Microsoft Account).
Use the custom scope in your client application
After the user has consented the custom Web API application, now the user can use the custom scopes (api://.../read
and api://.../write
in this example) in the user’s client application. (In this post, we use the OAuth code grant flow using a web client application.)
First register the new client application (which calls your custom Web API) in Entra ID management (on Azure Portal) as usual.
Now you generate client’s secret (app password) for this application.
Let’s start to consume the custom scope in your Web API using this client.
First, access the following URL with your web browser. (As you can see, the requesting scope is the previously registered custom scope api://f6da5452-7f05-4182-bd2d-feac1d2e86e2/read
.)
Here client_id
and redirect_uri
are for the web client application (not custom Web API application).
https://login.microsoftonline.com/common/oauth2/v2.0/authorize ?response_type=code &response_mode=query &client_id=b5b3a0e3-d85e-4b4f-98d6-e7483e49bffc &scope=api%3A%2F%2Ff6da5452-7f05-4182-bd2d-feac1d2e86e2%2Fread &redirect_uri=https%3a%2f%2flocalhost%2ftestwebclient
Note : In the real production, it’s also better to retrieve the id token (i.e,
response_type=id_token+code
), since your client will have to validate the returned token and check if the user has logged-in correctly.
This sample will skip this steps, because we focus on API scopes.
When you access this URL, the following login page will be displayed.
Once the login is succeeded with the user’s credential, the following consent is displayed.
As you can see, this shows that the client will use the permission of “Read test service data” (custom permission), which is a previously registered custom scope (api://f6da5452-7f05-4182-bd2d-feac1d2e86e2/read
).
After you approve this consent, the code
will be returned into your redirect URL as query string.
https://localhost/testwebclient?code=OAQABAAIAA...
Next, using code
value, you can request the access token for the requested resource (custom scope) with the following HTTP request.
This client_id
and client_secret
are each application id and application password of the user’s web client application.
HTTP Request
POST https://login.microsoftonline.com/common/oauth2/v2.0/tokenContent-Type: application/x-www-form-urlencodedgrant_type=authorization_code&code=OAQABAAIAA...&client_id=b5b3a0e3-d85e-4b4f-98d6-e7483e49bffc&client_secret=pmC...&scope=api%3A%2F%2Ff6da5452-7f05-4182-bd2d-feac1d2e86e2%2Fread&redirect_uri=https%3A%2F%2Flocalhost%2Ftestwebclient
HTTP Response
HTTP/1.1 200 OKContent-Type: application/json; charset=utf-8{ "token_type": "Bearer", "scope": "api://f6da5452-7f05-4182-bd2d-feac1d2e86e2/read", "expires_in": 3599, "ext_expires_in": 0, "access_token": "eyJ0eXAiOi..."}
Note : If you also want to get refresh token, you must add “offline_access” to the scopes.
Using the returned access token (access_token
property in above), you can call your custom Web API as follows and the API can verify the incoming token. (Later I show you how to verify this token in your custom Web API.)
GET https://localhost/testapiAuthorization: Bearer eyJ0eXAiOi...
Verify access token in your Web API
Now it’s in turn for your custom Web API.
In your Web API, you should verify the access token (make sure that it isn’t tampered), check claims, and proceed the request.
For instance, if your API is an e-mail application, it might process :
- Verify the access token
- Check claims
- The token timestamp is valid (start and expiration)
- The scope is valid
- The user has permission
and so on …
- Proceed the request
- Retrieve user from claims
- Get e-mail for that user
- Return e-mail (Respond)
As I mentioned in “How to verify id token in Entra ID“, the access token is also JWT in organization account (see below), then you can process verification and claim extraction with the same steps as id token.
See “How to verify id token in Entra ID“.
id token | access token | |
---|---|---|
organization account (Entra ID) | JWT | JWT |
consumer account (MSA) | JWT | Compact Tickets |
JWT of access token includes the following claims.
{ "aud": "f6da5452-7f05-4182-bd2d-feac1d2e86e2", "iss": "https://login.microsoftonline.com/3bc5ea6c-9286-4ca9-8c1a-1b2c4f013f15/v2.0", "iat": 1498037743, "nbf": 1498037743, "exp": 1498041643, "aio": "ATQAy/8DAA...", "azp": "b5b3a0e3-d85e-4b4f-98d6-e7483e49bffc", "azpacr": "1", "name": "Christie Cline", "oid": "fb0d1227-1553-4d71-a04f-da6507ae0d85", "preferred_username": "ChristieC@MOD776816.onmicrosoft.com", "scp": "read", "sub": "Pcz_ssYLnD...", "tid": "3bc5ea6c-9286-4ca9-8c1a-1b2c4f013f15", "ver": "2.0"}
Here I pick up some important claims :
aud
means the application id for targeting web api (here, custom Web API).nbf
(= not before) is the starting time of the token, andexp
is the expiration of the token.tid
means the tenant id of this logged-in user.scp
is the granted scopes.oid
is immutable user’s id. On the contrary,sub
is a pairwise id for application and user. Hence, when the application changes,sub
is also different. (oid
is always same for a user.)
I show you PHP sample code for checking these claims.
Here I don’t go so far, but see “How to verify id token in Entra ID” for details.
<?phpecho "The result is " . token_test("eyJ0eXAiOi...");// return 1, if token is valid// return 0, if token is invalidfunction token_test($token) { // 1 create array from token separated by dot (.) $token_arr = explode('.', $token); $header_enc = $token_arr[0]; $claim_enc = $token_arr[1]; $sig_enc = $token_arr[2]; // 2 base 64 url decoding $header =json_decode(base64_url_decode($header_enc), TRUE); $claim =json_decode(base64_url_decode($claim_enc), TRUE); $sig = base64_url_decode($sig_enc); // 3 period check $dtnow = time(); if($dtnow <= $claim['nbf'] or $dtnow >= $claim['exp'])return 0; // 4 audience check if (strcmp($claim['aud'], 'f6da5452-7f05-4182-bd2d-feac1d2e86e2') !== 0)return 0; // 5 scope check if (strcmp($claim['scp'], 'read') !== 0)return 0; // other checks if needed (lisenced tenant, etc) // Here, we skip these steps ... // // 6 check signature // // 6-a get key list $keylist =file_get_contents('https://login.microsoftonline.com/3bc5ea6c-9286-4ca9-8c1a-1b2c4f013f15/discovery/v2.0/keys'); $keylist_arr = json_decode($keylist, TRUE); foreach($keylist_arr['keys'] as $key => $value) {// 6-b select one keyif($value['kid'] == $header['kid']) {// 6-c get public key from key info $cert_txt = '-----BEGIN CERTIFICATE-----' . "n" . chunk_split($value['x5c'][0], 64) . '-----END CERTIFICATE-----'; $cert_obj = openssl_x509_read($cert_txt); $pkey_obj = openssl_pkey_get_public($cert_obj); $pkey_arr = openssl_pkey_get_details($pkey_obj); $pkey_txt = $pkey_arr['key'];// 6-d validate signature $token_valid =openssl_verify($header_enc . '.' . $claim_enc, $sig, $pkey_txt, OPENSSL_ALGO_SHA256); if($token_valid == 1)return 1; elsereturn 0; } }return 0;}function base64_url_decode($arg) { $res = $arg; $res = str_replace('-', '+', $res); $res = str_replace('_', '/', $res); switch (strlen($res) % 4) {case 0: break;case 2: $res .= "=="; break;case 3: $res .= "="; break;default: break; } $res = base64_decode($res); return $res;}?>
Calling another services in turn (OAuth On-Behalf-Of Flow)
As you can see above, the access token is for the some specific API (for “aud
“) and you cannot reuse the token for another API.
What if your custom Web API needs to call another API (e.g, Microsoft Graph API, other custom APIs, and so on) ?
In such a case, your API can convert to another token with OAuth on-behalf-of flow as follows. In this flow, no need to display the login UI again.
In this example, our custom Web API will connect to Microsoft Graph API and get e-mail messages of the logged-in user.
Note : For other APIs except for “Microsoft Graph”, please see “Entra ID – How to use custom scopes for admin consent“. (Added 02/07/2018)
Note : For on-behalf-of flow in Azure AD v1 endpoint, please refer my early post .
First, as the official document says (see here), you need to use tenant-aware endpoint when you use on-behalf-of flow with v2.0 endpoint. That is, the administrator consent (admin consent) is needed before requesting the on-behalf-of flow. (In this case, the user consent for custom Web API which is done in the previous section in this post is not needed.)
Before proceeding the admin consent, you must add the delegated permission for your custom Web API in Azure Portal. In this example, we add Mail.Read
permission as follows. (When you use admin consent, you cannot add scopes on requesting on the fly. You must set the permissions beforehand.)
Next the administrator in the user tenant must access the following URL using the web browser for administrator consent.
Note that xxxxx.onmicrosoft.com
can also be the tenant id (which is the Guid retrieved as “tid
” in the previous claims). f6da5452-7f05-4182-bd2d-feac1d2e86e2
is the application id of the custom Web API and https://localhost/testapi
is the redirect url of the custom Web API.
https://login.microsoftonline.com/xxxxx.onmicrosoft.com/adminconsent ?client_id=f6da5452-7f05-4182-bd2d-feac1d2e86e2 &state=12345 &redirect_uri=https%3A%2F%2Flocalhost%2Ftestapi
After logged-in with the tenant administrator, the following consent is displayed. When the administrator approves this consent, your custom Web API is registered in the tenant. As a result, all users in this tenant can use this custom Web API and custom scopes.
Note : You can revoke the admin-consented application in your tenant with Azure Portal. (Of course, the administrator privilege is needed for this operation.)
Now you can ready for the OAuth on-behalf-of flow in Entra ID.
First the user (non-administrator) gets the access token for the custom Web API and call the custom Web API with this access token. This flow is the same as above and I skip the steps here.
Then the custom Web API can request the following HTTP POST for Entra ID endpoint using the passed access token. (I note that eyJ0eXAiOi...
is the passed access token for this custom Web API, f6da5452-7f05-4182-bd2d-feac1d2e86e2
is the application id of your custom Web API, and itS...
is the application password of your custom Web API.)
This POST method is requesting the new access token for https://graph.microsoft.com/mail.read
(pre-defined scope).
POST https://login.microsoftonline.com/xxxxx.onmicrosoft.com/oauth2/v2.0/tokenContent-Type: application/x-www-form-urlencodedgrant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=eyJ0eXAiOi...&requested_token_use=on_behalf_of&scope=https%3A%2F%2Fgraph.microsoft.com%2Fmail.read&client_id=f6da5452-7f05-4182-bd2d-feac1d2e86e2&client_secret=itS...
The following is the HTTP response for this on-behalf-of request. As you can see, your custom Web API can take new access token.
HTTP/1.1 200 OKContent-Type: application/json; charset=utf-8{ "token_type": "Bearer", "scope": "https://graph.microsoft.com/Mail.Read https://graph.microsoft.com/User.Read", "expires_in": 3511, "ext_expires_in": 0, "access_token": "eyJ0eXAiOi..."}
The returned access token is having the scope for Mail.Read (https://graph.microsoft.com/mail.read
), and it’s not the application only token, but the user token by the logged-in user, although it’s done by the backend (server-to-server) without user interaction. (Please parse and decode this access token as I described above.)
Therefore, when your custom Web API connects to Microsoft Graph endpoint with this access token, the logged-in user’s e-mail messages will be returned to your custom Web API.
GET https://graph.microsoft.com/v1.0/me/messages ?$orderby=receivedDateTime%20desc &$select=subject,receivedDateTime,from &$top=20Accept: application/jsonAuthorization: Bearer eyJ0eXAiOi...
[Reference] App types for the Azure Active Directory v2.0 endpoint
https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-flows
Update History :
Dec 04, 2019 Update App Registration Portal (https://apps.dev.microsoft.com) to Azure Portal, because of App Registration Portal deprecation
Categories: Uncategorized
1
down vote
favorite
I’ve been playing around with API Management and it looks great. The only issue I have is that when calling an API you need to pass a subscription key, which is linked to a ‘User’. In the majority of use cases the caller of our APIs are Applications (back-end services).
Am I supposed to be creating a User account per Application, or is there another way for me to obtain a security key for the App?
LikeLike
Hi Chris-san, you can use api://{your app id}/.default as scope to get app-only token in the backend (without interactive login) same as the flow which I wrote in the following post.
https://blogs.msdn.microsoft.com/tsmatsuz/2016/10/07/application-permission-with-v2-endpoint-and-microsoft-graph/
However now we cannot add our custom “appRoles” in Web API’s manifest (I tried now, but it failed), and therefore we cannot set the custom application permissions with v2.0 endpoint.
https://blogs.msdn.microsoft.com/tsmatsuz/2015/04/09/azure-ad-backend-server-side-deamon-service/
Sorry, but we must wait until custom “appRole” is supported, and I hope we could use app-only token with application permissions by using “appRoles” and “pre-authenticated” capabilities in the future.
LikeLike
Hi,
I’m testing how we can use MSAL in our products but I ran into an issue, I described it here https://stackoverflow.com/questions/47611623/difference-between-company-account-and-msa-account-with-msal can you maybe explain the different behaviour between MSA and compan account?
LikeLike
Hi Pieter san,
As I’m writing in this post, did you consent your Web API application in the same tenant of your client application (as follows), before running your SPA JS script in client ?
https://login.microsoftonline.com/common/oauth2/v2.0/authorize
?response_type=id_token
&response_mode=form_post
&client_id=b7f92321-****-****-****-************
&scope=openid
&redirect_uri=
&nonce=abcdef
LikeLike
Hi,
Since I’m using MSAL.js to do login I didn’t do a seperate consent request, but if I look to the url MSAL.js generates I guess they do the same as the call you have
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?response_type=id_token&scope=api%3A%2F%2Fb7f92321-****-****-****-************%2Faccess_as_user%20openid%20profile&client_id=b7f92321-****-****-****-************&redirect_uri=http%3A%2F%2Flocalhost%3A4200&state=cae65cbe-42fe-4b71-aab8-023382263134&nonce=0151e8d5-c61f-46d2-9509-c9e0fc68f6ca&client_info=1&x-client-SKU=MSAL.JS&x-client-Ver=0.1.3&client-request-id=b2070a68-9ed6-4389-8ffa-8602123d34c7&prompt=select_account&response_mode=fragment
And with this call if I use MSA account it works fine and I do get to the consent screen, but if I’m using my company account with the same call I don’t get to the consent screen and i get the
AADSTS65005: The application ‘auth-test’ asked for scope ‘openid’ that doesn’t exist on the resource. Contact the app vendor. error back that’s what confusing me
LikeLike
Hi Pieter san,
You requesting url is doing : calling api application (app id = b7f92321-****-****-****-************) from the same client (client id = b7f92321-****-****-****-************).
Please add 2 applications, api application (A) and client application (B) in App Registration Portal, and you must add both applications (A and B) into your tenant (***.onmicrosoft.com tenant) with consent.
Please do the following steps.
1. Register your api application (A) with app registration portal. It needs “Web API” platform, because it is an api application.
2. Add “Web” platform to previous application A. Because your tenant must include this application A by the consent flow beforehand.
3. Please consent this application A into your tenant with your browser. Access the following url with your browser, as I wrote before. (The following client_id is one of your api application.)
https://login.microsoftonline.com/common/oauth2/v2.0/authorize
?response_type=id_token
&response_mode=form_post
&client_id=b7f92321-****-****-****-************
&scope=openid
&redirect_uri=
&nonce=abcdef
4. Register your client application (B) with app registration portal.
5. Create your application with MSAL.js as you wrote. Note that your client id (B) is different from your api application (A) in this case.
LikeLike
I reply here since I can’t reply on your reply 🙂
Steps 1 and 2 I already did, if come to step 3 to consent application A into tenant, I get the following error when I logged on
Cannot POST /
I took the url you gave me, and edited the client_id to the application id I have. I also executed steps 4 and 5 but to no result, I still get the AADSTS65005 when I use a company account, with MSA account it all works
LikeLike
Hi Pieter san,
Sorry again, but can you please tell me what kind of error and message is displayed when you access to step3. You are saying : After you logged-in, the consent UI is not displayed and the error occurs. Is that right ?
If you have Fiddler, please trace this flow and tell me what kind of url or page is there. The error message will be in uri fragment, or be displayed in page UI.
LikeLike
Hi Tsuyoshi, I’m using azure ad b2c authentication flow with my php application. My problem is the azure ad JWT access tokens has x5c entry where B2C doesn’t have this entry. So how do I validate the azure ad b2c token with the digital signature. I did period check and audience check successfully.
LikeLike
Hi Hirantha-san, you might see e (exponent) and n (modulus), and you can generate public key (x5c) from these exponent and modulus.
https://stackoverflow.com/questions/16993838/openssl-how-can-i-get-public-key-from-modulus
Please see the following note for B2C token validation.
https://stackoverflow.com/questions/45623178/wheres-the-key-for-my-azure-ad-b2c-token
LikeLike
Hi Tsuyoshi, I followed above link in stack overflow and generated the public key. It generates a public key as follows.
—–BEGIN PUBLIC KEY—–\r\n
MIIBGzANBgkqhkiG9w0BAQEFAAOCAQgAMIIBAwKB+wC1UpS1zGf2u3lp9jZYU29T\r\n
o+UUyCAJq2xneXhGMrEfSOB0uroJJxvQ/dkZdhs/Ne+aYuJZmLOluHSq6nmPOc68\r\n
6sOHD33TznzgFKALzdhpyNY2oQre6vGHITaNaPBGbY33lYaBvmbMTleZki+S/Jee\r\n
ftfx2+AU8XlDAieAawMbwvIe4laVoOGsQ8sxbjAe8fTNkK5EwBEgmaaqIzLdUPh+\r\n
—–END PUBLIC KEY—–
But openssl_verify returns 0.
LikeLike
Sorry that I don’t have time to check by myself, but if you’re using Crypt_RSA, how about directly call verify() method with Crypt_RSA ? (same error occurs ?)
I’ll check by myself what’s happening when I’m available…
LikeLike
Sorry for late response. Now I checked using C# (sorry, but I didn’t use PHP), and verification is successful using only modulus (n) and exponent (e) with .NET RSACryptoServiceProvider class.
You must get modulus and exponent from B2C policy endpoint. In my case, I got id token using my B2C sign-up policy and then I got these values (modulus and exponent) from https://login.microsoftonline.com/te/tsmatsuzb2c01.onmicrosoft.com/B2C_1_signup01/discovery/v2.0/keys . (Make sure to check if “kid” is the same value.)
See https://stackoverflow.com/questions/34403823/verifying-jwt-signed-with-the-rs256-algorithm-using-public-key-in-c-sharp for programming with RSACryptoServiceProvider class.
I hope your success.
LikeLike
Hi Hirantha-san. I also ran the id token (JWT) validation with php. Here I used Crypt_RSA in phpseclib, and the following is complete code. Thanks,
new Math_BigInteger(base64_url_decode($modulus), 256),
‘e’ => new Math_BigInteger(base64_url_decode($exponent), 256),
];
$rsa->loadKey($public);
$rsa->setHash(‘sha256’);
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
$hash = new Crypt_Hash(‘sha256’);
echo $rsa->verify($headers_enc . ‘.’ . $claims_enc, $sig) ? ‘verified’ : ‘unverified’;
function base64_url_decode($arg) {
$res = $arg;
$res = str_replace(‘-‘, ‘+’, $res);
$res = str_replace(‘_’, ‘/’, $res);
switch (strlen($res) % 4) {
case 0:
break;
case 2:
$res .= “==”;
break;
case 3:
$res .= “=”;
break;
default:
break;
}
$res = base64_decode($res);
return $res;
}
?>
LikeLike
Hi Tsuyoshi-san. Thank you very much for your kind response. I fixed my issue. But I have a concern about the life time of the public key from from https://{issuer url}/.well-known/openid-configuration. Do we need to call it every time, does public key expire, is it valid to store public key in cache and reuse ?
LikeLike
I was extremely pleased to uncover this web site. I wanted to thank you for
ones time due to this wonderful read!! I definitely enjoyed every little bit of it
and I have you bookmarked to look at new information on your website.
LikeLike
This is the perfect blog for anyone who hopes to understand
this topic. You understand so much its almost hard to
argue with you (not that I really will need to…HaHa).
You definitely put a new spin on a subject that’s been written about for decades.
Excellent stuff, just great!
LikeLike
I’m still learning from you, but I’m improving
myself. I definitely enjoy reading everything
that is written on your blog.Keep the aarticles coming.
I loved it!
LikeLike
I was wondering if you ever thought of changing the structure of
your blog? Its very well written; I love what youve got to say.
But maybe you could a little more in the way of content
so people could connect with it better. Youve got an awful lot of text for only having 1 or 2 pictures.
Maybe you could space it out better?
LikeLike
What’s up mates, how is the whole thing,
and what you want to say on the topic of this post, in my view its genuinely awesome in favor of me.
LikeLike
of course like your web site however you have to check the spelling on several of
your posts. Many of them are rife with spelling issues and I to find it very troublesome to
inform the reality on the other hand I’ll definitely come back
again.
LikeLike
Hello, its nice post about media print, we all understand media is a wonderful source of facts.
LikeLike
Круто! Некоторые пункты очень тонко подмечены!
Я ценю, что ты сочинил эти записи и остальные данные они
невероятно хороши.
LikeLike
Добрый день! Кто-то из подписчиков моей страницы в MySpace подписан на автора этого веб-сайта и
может перейти туда, чтобы посмотреть увлекательные
материалы? Я оставил закладку и обязательно поделюсь с моими подписчиками!
Отличный блог, гармоничный стиль и дизайн.
LikeLike
This is my first time pay a visit at here and i am in fact impressed to read
everthing at single place.
LikeLike
Some helpful information within really. Nice to see your site
LikeLike
Wow, that’s what Ι wаs searching fоr, what a informаtion! presdent herе at this weblog,
thanks admin ᧐f thіs website.
LikeLike
This piece of writing presents clear idea in support of the
new users of blogging, that genuinely how to do running a blog.
LikeLike
Автор грамотно ведет диалог по вопросу
этой мысли именно в данном персональном блоге,
я прочитал все, и в данный момент мне также
хочется прокомментировать статью.
LikeLike
Все любят вещи, которые вы,
ребята, так мастерски делаете.
Отличная работа и актуальные отчетности!
Ваш блог потрясающий Я добавил адрес страницы в закладки!
Ребята, вы настоящие профи, огромный респект!
LikeLike
Вау! Я действительно наслаждаюсь скачивая
шаблоны и темы этого сайта. Они просты, но эффективны для
оформления. Часто тяжело найти “идеальный баланс” между комфортом использования и внешним видом ресурса.
Я должен сказать, что этот человек проделал фантастическую работу над материалом.
К тому же, процесс загрузки супер скоростной, у меня браузер Firefox.
Выдающийся блог!
LikeLike
Эй, приветик! Кто-то из подписчиков
моей страницы в Твиттер подписан на автора этого блога и может перейти туда, чтобы оценить отличные
материалы? Я сделал закладку и
обязательно поделюсь с моими подписчиками!
Замечательный блог, удивительный дизайн и стиль.
LikeLike
Hi to every one, the contents present at this website are in fact amazing for people
knowledge, well, keep up the nice work fellows.
LikeLike
Good blog ppost
LikeLike