I am trying to set the redirect_uri in Azure B2C. I have a language field in the Url like this:
https://mydomain/de-de/projects
https://mydomain/en-us/projects
https://mydomain/sv-se/projects
https://mydomain/ar-sa/projects
...
and to be correctly redirected, I have to add all the possibilities to the B2C Reply URLs and I am only limited to 20 max.
Is there a way to add variables to the redirect_uri?
Something like:
https://mydomain/:lang/projects
where ":lang" is a variable the could take any value.
////////////////////////////////////
Solution
The tricky solution was to manipulate the state and inject it with the returned URL due to the fact that it will be sent back after the login/signup response. createLoginUrl() method:
let url = that.loginUrl
+ '?response_type='
+ response_type
+ '&client_id='
+ encodeURIComponent(that.clientId)
+ '&state='
+ encodeURIComponent((state) + 'url' + returnedUrl)
+ '&redirect_uri='
+ encodeURIComponent(window.location.origin)
+ '&scope='
+ encodeURIComponent(that.scope);
so here I split the state with 'url' word so I can read it again after the response came.
encodeURIComponent((state) + 'url' + returnedUrl)
An important details redirect_uri, it should be the same origin:
'&redirect_uri=' + encodeURIComponent(window.location.origin)
and this URL should be added to the returned URL in Azure B2C application.
Now I can split it again in tryLogin() method:
const statePartsWithUrl = (parts['state'] + '').split('url');
window.location.href = statePartsWithUrl[1];
and it works perfectly.
////-------------------------------------
Edit : 1.2.2019
const statePartsWithUrl = (parts['state'] + '').split('url');
let state = '';
let returnedUrl = '';
if (statePartsWithUrl != null) {
state = statePartsWithUrl[0];
returnedUrl = statePartsWithUrl[1];
}
Here is the splitting of the state to read the information from it in method tryLogin(options)
Yeah so as you found out, you can't currently add wildcards to reply URLs in B2C.
This may be due to security concerns defined in the OAuth 2.0 Threat Model and Security Considerations RFC.
In it, the suggested counter-measure against Open Redirect Attacks is to have the client register the full redirect URI.
There is also no way to create apps programmatically: https://feedback.azure.com/forums/169401-azure-active-directory/suggestions/19975480-programmatically-register-b2c-applications.
So sadly the manual way is the only way at the moment. But be sure to go upvote the feature request on User Voice.
I actually even tried to manually edit an app via Graph Explorer:
{
"odata.error": {
"code": "Request_BadRequest",
"message": {
"lang": "en",
"value": "Updates to converged applications are not allowed in this version."
},
"date": "2018-01-08T12:00:00",
"requestId": "208e7159-d459-42ec-8bb7-000000000000",
"values": null
}
}
As you suggested in the comments, one way to work around this problem would be to use a single static redirect URI and keep the language/culture in the state/a cookie, and then do the redirect to the language-specific version after the user is returned to the app.
Related
I'm seeking some help to understand how to implement an azure oauth login on my chrome webextension. I've found a very useful suggestion here on SO by #Rinor how to use user.identity in with Azure authentication and I think I'm half way to succeed.
Using the code below I get the login popup and I can easily sign-in with username and password. As a response from Azure I get an url that contain the token, I then extranct the token with a regex and I save it to the crhorme.storage.
The problem is that I'm now stuck. How should I then proceed now to get the user details (I only need the email address or the username) actually. I do not understand if now I need another ajax call to /oauth2/v2.0/token to get an additional token...and if so, how should I pass the token I got from the initial call to /oauth2/v2.0/authorize.
Does anyone have any idea on how to proceed? Any help would be more than welcome 🙏
Thanks a lot in advance
chrome.identity.launchWebAuthFlow(
{
url: 'https://login.microsoftonline.com/' + tenant_id + '/oauth2/v2.0/authorize?' + // <= here tenant id or just common
'response_type=token' +
'&response_mode=fragment' +
'&prompt=login' +
'&client_id=' + client_id + // <= here client id from azure console
'&redirect_uri=' + redirectUrl +
'&scope=openid https://management.azure.com/user_impersonation profile',
interactive: true
},
function (responseWithToken) {
// the access token needs to be extracted from the response.
console.log(responseWithToken);
let token = responseWithToken.match(/(?<=access_token=).*(?=&token_type)/);
chrome.storage.local.set({ "azure_token": token }, function () {
console.log('Value is set');
});
}
);
// What next? :S
If you want to get the username of the user, you could decode the access_token what is the response after the user login. And the upn in the claims is the username of the user, maybe a phone number, email address, or unformatted string.
For more details about access token, see here.
I have created 2 web apps that are being hosted in Azure. One app (A) is an IdentityServer4 provider and the other (B) is an app that uses A as an external login provider via OpenId Connect. In the code for A, I configured IdentityServer4 to require a specific redirect URI for B's sign-in calls:
namespace A.IdentityServer4Config
{
public static class Clients
{
public static IEnumerable<Client> GetClients(IConfiguration configuration)
{
return new List<Client>
{
new Client
{
ClientId = "B",
ClientSecrets = {new Secret("hashed secret for B")},
AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
AllowedScopes = {"openid", "profile", ...},
RedirectUris = {"https://B.azurewebsites.net/Account/oidc-callback"},
RequireConsent = false,
AccessTokenLifetime = 10 * 60,
},
};
}
}
}
Within Startup.ConfigureServices:
var identityServerBuilder = services
.AddIdentityServer(options => {...})
...
.AddInMemoryClients(Clients.GetClients(configuration))
...;
Notice that the redirect URI I specified is an HTTPS URI.
In the code for B, I used one of the AddOpenIdConnect() overloads in Microsoft.Extensions.DependencyInjection.OpenIdConnectExtensions to configure how the sign-in call should proceed:
services.AddAuthentication(options => {...})
...
.AddOpenIdConnect(
"oidc",
options =>
{
...
options.Authority = "https://A.azurewebsites.net";
options.RequireHttpsMetadata = true;
options.ClientId = "B";
options.ClientSecret = "secret for B";
options.ResponseType = "code";
options.CallbackPath = "/Account/oidc-callback"; // !!!
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
...
}
);
The line commented with "!!!" is where the redirect URI for the sign-in request is configured. Notice that it is required to be a path relative to the application's domain. You cannot specify the URI in full (I tried; if you do this, then the application crashes when it starts up). Since you cannot specify the URI in full, this property cannot be used to require that the redirect URI be HTTPS. (RemoteAuthenticationOptions.CallbackPath is a Microsoft.AspNetCore.Http.PathString. It is described in official documentation as follows: "The request path within the application's base path where the user-agent will be returned. The middleware will process this request when it arrives.")
When I try to log in at B, I am taken to an error page on A. When I look at the logs generated by IdentityServer4 on A, it is clear that the reason for the error is that the redirect URI requested by B does not match the expected one. The only difference between the two URIs is that the one A is expecting to see is an HTTPS URI and the one B is specifying is an HTTP (no "S") URI:
2018-08-16 15:20:41.307 +00:00 [Error] IdentityServer4.Endpoints.AuthorizeEndpoint: Request validation failed
2018-08-16 15:20:41.307 +00:00 [Information] IdentityServer4.Endpoints.AuthorizeEndpoint: {
"ClientId": "B",
"AllowedRedirectUris": [
"https://B.azurewebsites.net/Account/oidc-callback"
],
"SubjectId": "anonymous",
"RequestedScopes": "",
"Raw": {
"client_id": "B",
"redirect_uri": "http://B.azurewebsites.net/Account/oidc-callback",
"response_type": "code",
"scope": "openid profile ...",
"response_mode": "form_post",
"nonce": "[omitted]",
"state": "[omitted]",
"x-client-SKU": "ID_NETSTANDARD1_4",
"x-client-ver": "5.2.0.0"
}
}
2018-08-16 15:20:41.307 +00:00 [Information] IdentityServer4.Events.DefaultEventService: {
"Name": "Token Issued Failure",
"Category": "Token",
"EventType": "Failure",
"Id": 2001,
"ClientId": "B",
"Endpoint": "Authorize",
"Scopes": "",
"Error": "unauthorized_client",
"ErrorDescription": "Invalid redirect_uri",
"ActivityId": "[omitted]",
"TimeStamp": "2018-08-16T15:20:41Z",
"ProcessId": 10408,
"LocalIpAddress": "[omitted]",
"RemoteIpAddress": "[omitted]"
}
I do not know why B's request to A defaults to HTTP for the redirect URI. I do not know how to make it HTTPS instead. I could relax the requirement that the redirect URI be HTTPS, but this seems like the wrong thing to do. I would like to know how to get B to specify an HTTPS URI in its request to A. From the description of RemoteAuthenticationOptions.CallbackPath in the documentation, I would suppose that this should be achieved by making sure "the application's base path" is HTTPS; however, I have no idea how to do this and have had no success searching the internet.
When I run these applications locally, everything works correctly, because when B is running locally, the requests it makes specify an HTTPS redirect URI (as I wish them to). I do not know why it behaves differently when running locally and when running on Azure.
What I have tried
As mentioned above, I tried specifying the redirect URI fully (instead of in relative terms). This did not work - it causes the application to crash when it starts up.
I also tried setting the CallbackPath type to a PathString constructed using PathString.FromUriComponent() (using a variety of absolute URIs). This did not work. The protocol and domain parts of the URI were discarded and replaced with what they would have been (i.e. "http" and the appropriate domain) if I had passed a relative URI as a string.
I looked through the Startup classes in the various example MVC clients found at https://github.com/IdentityServer/IdentityServer4.Samples. I did not see anything that was obviously a solution to this problem.
It turns out that on Azure, whether the redirect URI generated is HTTP or HTTPS is determined by the "HTTPS Only" setting in the SSL configuration "blade".
In the Azure web portal, select the app service (B). Under "Settings", select "SSL settings". The SSL configuration "blade" opens. The first option that appears is "HTTPS Only". It defaults to "off". Change it to "on".
I just recently building an plugin in which I need to integrate Google Login. I searched and found chrome.identity to authenticate user using google account but that does not work well.
So I came across a solution by using this code below
var manifest = chrome.runtime.getManifest();
var clientId = encodeURIComponent(manifest.oauth2.client_id);
var scopes = encodeURIComponent(manifest.oauth2.scopes.join(' '));
var redirectUri = encodeURIComponent('urn:ietf:wg:oauth:2.0:oob:auto');
var url = 'https://accounts.google.com/o/oauth2/v2/auth' +
'?client_id=' + clientId +
'&response_type=code' +
'&redirect_uri=' + redirectUri +
'&scope=' + scopes;
var RESULT_PREFIX = ['Success', 'Denied', 'Error'];
chrome.tabs.create({'url': 'about:blank'}, function(authenticationTab) {
chrome.tabs.onUpdated.addListener(function googleAuthorizationHook(tabId, changeInfo, tab) {
if (tabId === authenticationTab.id) {
var titleParts = tab.title.split(' ', 2);
var result = titleParts[0];
if (titleParts.length == 2 && RESULT_PREFIX.indexOf(result) >= 0) {
chrome.tabs.onUpdated.removeListener(googleAuthorizationHook);
chrome.tabs.remove(tabId);
var response = titleParts[1];
switch (result) {
case 'Success':
// Example: id_token=<YOUR_BELOVED_ID_TOKEN>&authuser=0&hd=<SOME.DOMAIN.PL>&session_state=<SESSION_SATE>&prompt=<PROMPT>
console.log("suc:"+response);
break;
case 'Denied':
// Example: error_subtype=access_denied&error=immediate_failed
console.log("denied:"+response);
break;
case 'Error':
// Example: 400 (OAuth2 Error)!!1
console.log("error:"+response);
break;
}
}
}
});
chrome.tabs.update(authenticationTab.id, {'url': url});
});
In which if I remove v2 from the url variable then it always gives error in the turn with id_token but if I add v2 then its success and return code.
So now I read google documentation which said that now create a post request using client_id and client_secret but I chrome app create credential on google console which does not have client_secret
Now what should I do ? Is there anything that I missed or do wrong here and I also came across one of the chrome extension Screencastify use google login.
Can anyone explain how they do it ?
There's an official OAuth tutorial here for Chrome extensions/apps which you can refer to.
There's another blog tutorial here:
Step 1: Copy library
You will need to copy the oauth2 library into your chrome extension root into a directory called oauth2.
Step 2: Inject content script
Then you need to modify your manifest.json file to include a content script at the redirect URL used by the Google adapter. The "matches" redirect URI can be looked up in the table above:
"content_scripts": [
{
"matches": ["http://www.google.com/robots.txt*"],
"js": ["oauth2/oauth2_inject.js"],
"run_at": "document_start"
}
],
Step 3: Allow access token URL
Also, you will need to add a permission to Google's access token granting URL, since the library will do an XHR against it. The access token URI can be looked up in the table above as well.
"permissions": [
"https://accounts.google.com/o/oauth2/token"
]
Step 4: Include the OAuth 2.0 library
Next, in your extension's code, you should include the OAuth 2.0 library:
<script src="/oauth2/oauth2.js"></script>
Step 5: Configure the OAuth 2.0 endpoint
And configure your OAuth 2 connection by providing clientId, clientSecret and apiScopes from the registration page. The authorize() method may create a new popup window for the user to grant your extension access to the OAuth2 endpoint.
var googleAuth = new OAuth2('google', {
client_id: '17755888930840',
client_secret: 'b4a5741bd3d6de6ac591c7b0e279c9f',
api_scope: 'https://www.googleapis.com/auth/tasks'
});
googleAuth.authorize(function() {
// Ready for action
});
Step 6: Use the access token
Now that your user has an access token via auth.getAccessToken(), you can request protected data by adding the accessToken as a request header
xhr.setRequestHeader('Authorization', 'OAuth ' + myAuth.getAccessToken())
or by passing it as part of the URL (depending on the server implementation):
myUrl + '?oauth_token=' + myAuth.getAccessToken();
Note: if you have multiple OAuth 2.0 endpoints that you would like to authorize with, you can do that too! Just inject content scripts and add permissions for all of the providers you would like to authorize with.
And here's the actual github sample using those concepts.
This question is actually a continuous question of this SO question of mine. I am trying to get access_token and id_token from Identityserver4 by using Authorization code flow.
But, If I try to access "Authorize" endpoint, I got 405 (method not allowed) HTTP error.
HTTP GET Request
http://localhost:2000/connect/authorize?
client_id=client
&client_secret=secret
&grant_type=authorization_code
&username=admin
&password=admin
&response_type=id_token+token
&scope=openid+profile+offline_access
Client:
new Client
{
ClientId = "client",
ClientSecrets = { new Secret("secret".Sha256())},
AllowedGrantTypes = new List<string> { "authorization_code" },
AccessTokenType = AccessTokenType.Jwt,
AllowedScopes = { StandardScopes.OpenId.Name, "api1" }
}
User:
new InMemoryUser
{
Subject = "1",
Username = "admin",
Password = "admin"
}
My question is, How to call authorize endpoint to get access_token and id_token? What's wrong in my "client" and "user" configuration?
Two ideas:
The HTTP 405 error can be due to the web browser's same origin policy. Your client looks like a confidential client not a browser-based client, though, and that means the same origin policy does not apply, unless you are mistakenly making that request through a web browser.
That HTTP 405 error can also happen when you use an HTTP verb that is not allowed. For instance, if you use a POST when the URL allows only a GET. Make 100% sure that you are making a GET request.
You have several issues. You are mixing up multiple flows.
1) If you want to get an id_token back from the Authorize endpoint (and not the Token endpoint) you need to use Hybrid flow... not authorization code flow. See here. So you'd need to change your response type accordingly. If your client was a SPA you could use implicit flow and get an id_token and access_token from the Authorize endpoint - but not authorization code flow.
2) client_secret is not a parameter for the Authorize endpoint. Neither is grant_type. See here for the valid parameters.
3) you don't send username and password to the Authorize endpoint under any circumstances. If you are using resource owner flow you'd send them to the Token endpoint - but never Authorize. See the above link with the description of valid parameters.
So you can switch to hybrid flow and change your code to this:
http://localhost:2000/connect/authorize?
client_id=client
&redirect_uri=<add redirect uri>
&response_type=code+id_token+token
&scope=openid+profile+api1
&state=...
The response from this call will include id_token and access_token.
new Client
{
ClientId = "client",
ClientName = "Your Client",
AllowedGrantTypes = GrantTypes.Hybrid,
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = { "<add redirect uri>" },
PostLogoutRedirectUris = { "<add post logout redirect uri>" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
}
};
I created an account in the Docusign Sandbox to evaluate the product. My main goal is to create a signing group but before that I would like to get the list of signing groups with RestSharp.
[EDIT]
docusign.configureApiClient("https://demo.docusign.net/restapi");
var login = docusign.LoginDocusign(USERNAME, PASSWORD);
var client = new RestClient(login.BaseUrl);
var request = new RestRequest("signing_groups", Method.GET);
string authHeader = "{\"Username\":\"" + USERNAME + "\", \"Password\":\"" + PASSWORD + "\", \"IntegratorKey\":\"" + INTEGRATOR_KEY + "\"}";
request.AddHeader("X-DocuSign-Authentication", authHeader);
IRestResponse response = client.Execute(request);
var content = response.Content;
Debug.WriteLine(content);
However the content returns
error code : "ACCOUNT_LACKS_PERMISSIONS"
message: "This Account lacks sufficient permissions."
I thought demo accounts have almost the same permissions as a premium account. Is there a setting somewhere to enable this?
I am certain that the authentication is correct when I request for list of groups, the content returns all of my groups.
var request = new RestRequest("groups", Method.GET);
I'm not familiar with RestSharp, but my suspicion is that there's a problem with the request.
To diagnose: first start with making a call to DocuSign and use the Request Log facility to see what is being sent.
If the incoming request can't be matched to your account, or fails some initial filters, then the request will not reach your account. In that case, use requestb.in (free) to see what you're sending.
Also, I don't see where you are requesting the accountId and base URL from DocuSign. (Using https://demo.docusign.net/restapi/v2/login_information)
That's the first step for an API integration since you can't predict which platform the user's account is running on.