I am trying to use the NelmioApiDocBundle for a Symfony 3.4 projects API documentation, while also trying to wrap my head around OAuth 2 authorization for the project API access to begin with.
So far I've followed this tutorial on how to get FOSOAuthServerBundle working. So far I can
1.) create a client using the command line command:
php bin/console fos:oauth-server:create-client --redirect-uri="___" --grant-type="authorization_code" --grant-type="password" --grant-type="refresh_token" --grant-type="token" --grant-type="client_credentials"
2.) I can also get an access token manually by visiting this url on my server
http://127.0.0.1:8000/oauth/v2/token?client_id=______&client_secret=________&grant_type=client_credentials
3.) I can use the token to access areas of my Symfony project requiring OAuth Access by including the token in a GET parameter
However, in the NelmioApiDocBundle Authorizations I cannot get this to work to completion. Here is a screenshot:
If enter my client_id and secret key it takes me to the Login Page, as expected. I can enter my login information and in takes me to the Approve or Deny Page, as expected. At this point if I click either Approve or Deny it tries to use a "redirect_uri" of http://localhost:3200/oauth2-redirect.html. No matter what I do I cannot change the redirect URI.
How to I get the a proper redirect URI?
Ok, this was actually easily fixed. You need to add a single line:
oauth2RedirectUrl: 'URLhere',
to the file init-swagger-ui.js which is located (Symfony 3.4) in web/bundles/nelmioapidoc/
The final file ended up looking like this:
window.onload = () => {
const data = JSON.parse(document.getElementById('swagger-data').innerText);
const ui = SwaggerUIBundle({
oauth2RedirectUrl: 'URLhere',
spec: data.spec,
dom_id: '#swagger-ui',
validatorUrl: null,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: 'StandaloneLayout'
});
window.ui = ui;
};
Also you likely are going to want to download the file oauth2-redirect.html from the Swagger project to include for the actual redirect.
Related
Manifest version 3 for Chrome extensions have been killing me lately. Been able to navigate around it so far, but this one has really stumped me. I'm trying to use Firebase authentication for a Chrome extension, specifically with 3rd party auth providers such as Google and Facebook. I've setup the Firebase configuration for Login with Google and created a login section in the options page of the Chrome extension and setup the Firebase SDK.
Now, there are two login options when using an auth provider, signInWithRedirect and signInWithPopup. I've tried both of these and both have failed for different reasons. signInWithRedirect seems like a complete dead end as it redirects to the auth provider, and when it attempts to redirect back to the chrome-extension://.../options.html page, it just redirects to "about:blank#blocked" instead.
When attempting to use signInWithPopup, I instead get
Refused to load the script 'https://apis.google.com/js/api.js?onload=__iframefcb776751' because it violates the following Content Security Policy directive: "script-src 'self'". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.
In v2, you could simply add https://apis.google.com to the content_security_policy in the manifest. But in v3, the docs say
"In addition, MV3 disallows certain CSP modifications for extension_pages that were permitted in MV2. The script-src, object-src, and worker-src directives may only have the following values:"
self
none
Any localhost source, (http://localhost, http://127.0.0.1, or any port on those domains)
So is there seriously no way for a Google Chrome extension to authenticate with a Google auth provider through Google's Firebase? The only workaround I can think of is to create some hosted site that does the authentication, have the Chrome extension inject a content script, and have the hosted site pass the auth details back to the Chrome extension through an event or something. Seems like a huge hack though and possibly subject to security flaws. Anyone else have ideas??
Although it was mentioned in the comments that this works with the Google auth provider using chrome.identity sadly there was no code example so I had to figure out myself how to do it.
Here is how I did it following this tutorial:
(It also mentions a solution for non-Google auth providers that I didn't try)
Identity Permission
First you need permission to use the chrome identity API. You get it by adding this to your manifest.json:
{
...
"permissions": [
"identity"
],
...
}
Consistent Application ID
You need your application ID consistent during development to use the OAuth process. To accomplish that, you need to copy the key in an installed version of your manifest.json.
To get a suitable key value, first install your extension from a .crx file (you may need to upload your extension or package it manually). Then, in your user data directory (on macOS it is ~/Library/Application\ Support/Google/Chrome), look in the file Default/Extensions/EXTENSION_ID/EXTENSION_VERSION/manifest.json. You will see the key value filled in there.
{
...
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgFbIrnF3oWbqomZh8CHzkTE9MxD/4tVmCTJ3JYSzYhtVnX7tVAbXZRRPuYLavIFaS15tojlRNRhfOdvyTXew+RaSJjOIzdo30byBU3C4mJAtRtSjb+U9fAsJxStVpXvdQrYNNFCCx/85T6oJX3qDsYexFCs/9doGqzhCc5RvN+W4jbQlfz7n+TiT8TtPBKrQWGLYjbEdNpPnvnorJBMys/yob82cglpqbWI36sTSGwQxjgQbp3b4mnQ2R0gzOcY41cMOw8JqSl6aXdYfHBTLxCy+gz9RCQYNUhDewxE1DeoEgAh21956oKJ8Sn7FacyMyNcnWvNhlMzPtr/0RUK7nQIDAQAB",
...
}
Copy this line to your source manifest.json.
Register your Extension with Google Cloud APIs
You need to register your app in the Google APIs Console to get the client ID:
Search for the API you what to use and make sure it is activated in your project. In my case Cloud Firestore API.
Go to the API Access navigation menu item and click on the Create an OAuth 2.0 client ID... blue button.
Select Chrome Application and enter your application ID (same ID displayed in the extensions management page).
Put this client ID in your manifest.json. You only need the userinfo.email scope.
{
...
"oauth2": {
"client_id": "171239695530-3mbapmkhai2m0qjb2jgjp097c7jmmhc3.apps.googleusercontent.com",
"scopes": [
"https://www.googleapis.com/auth/userinfo.email"
]
}
...
}
Get and Use the Google Auth Token
chrome.identity.getAuthToken({ 'interactive': true }, function(token) {
// console.log("token: " + token);
let credential = firebase.auth.GoogleAuthProvider.credential(null, token);
firebase.auth().signInWithCredential(credential)
.then((result) => {
// console.log("Login successful!");
DoWhatYouWantWithTheUserObject(result.user);
})
.catch((error) => {
console.error(error);
});
});
Have fun with your Firebase Service...
I am trying to authenticate an app with Azure AD. It's all good in localhost, it redirects to Azure AD where I enter details to authenticate, and it sends back the token that allows to view the resource.
Everything managed behind the scenes with the Microsoft.AspNetCore.Authentication.AzureAD.UI 3.1.10 in an aspnetcore 3.1 application.
My app runs on http://localhost:5000 and I can configure the redirectUri/replyUri at Azure AD for that application to support this url. All good.
The problem is in a different environment when my app runs in a service fabric cluster.
I can see the problem
AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application
When I inspect the url I can see that the redirect_uri has some url like this http://12.12.12.12/signin-oidc
The problem is double here. First of all I don't know which IP the cluster is gonna assign. Second, it is http, not https, and that's not supported by Azure AD.
Luckily my app has an external Url with a reverse proxy I can use to access. Something like https://myservicefabriccluster.com/MyApp
That Url I could configure as my redirect_uri in both my application and Azure AD, but I don't know how to do so.
My code has something like this:
services
.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
where I bind my settings.
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "76245c66-354e-4a94-b34d-...",
"TenantId": "59c56bd4-ce18-466a-b515-..."
},
I can see the AzureADOptions supports some other parameters such as Domain (not needed) or CallbackPath (which by default is ok being /signin-oidc) but there is nothing similar to ReplyUrl or RedirectUri where I can specify an absolute URL as the callback.
I have found a few similar issues without an answer. Others suggest some kind of tricks like a middleware that rewrites that parameter just before redirecting to Azure AD.
Certainly there must be an easier way to deal with this problem that I expect is not so strange. Any help please?
The solution to overwrite redirect_uri parameter with a custom value is to use the Events available in OpenIdConnect library. This library should be available as it's a dependency for Microsoft.AspNetCore.Authentication.AzureAD.UI, so this is my solution that, in addition to the standard properties for AzureADOptions it adds a flag to determine whether the redirect uri must be overwritten and a value to do so. I hope it's self explanatory
services
.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => configuration.Bind("AzureAd", options));
var isCustomRedirectUriRequired = configuration.GetValue<bool>("AzureAd:IsCustomRedirectUriRequired");
if (isCustomRedirectUriRequired)
{
services
.Configure<OpenIdConnectOptions>(
AzureADDefaults.OpenIdScheme,
options =>
{
options.Events =
new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = async ctx =>
{
ctx.ProtocolMessage.RedirectUri =
configuration.GetValue<string>("AzureAd:CustomRedirectUri");
await Task.Yield();
}
};
});
}
services
.AddAuthorization(
options =>
{
options.AddPolicy(
PolicyConstants.DashboardPolicy,
builder =>
{
builder
.AddAuthenticationSchemes(AzureADDefaults.AuthenticationScheme)
.RequireAuthenticatedUser();
});
});
And the appsettings.json would have something like this:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "76245c66-354e-4a94-b34d-...",
"TenantId": "59c56bd4-ce18-466a-b515-..."
"IsCustomRedirectUriRequired": true,
"CustomRedirectUri": "https://platform-cluster-development01.cubictelecom.com:19008/Scheduler/WebApi/signin-oidc"
},
Notice the IsCustomRedirectUriRequired and CustomRedirectUri are my custom properties that I read explicitly in order to overwrite (or not) the redirect uri query parameter when being redirected to the identity provider (i.e: Azure AD)
Looking at this, you should be configuring the public URL as the redirect URI, which is a value such as this:
https://myservicefabriccluster.com/MyApp
It looks like that the above library does not easily support this, and forces the redirect URI to be based on the HTTP listening URL of the code. As part of resolving this it is worth considering how you are writing your code:
This line of code indicates that your app is limited to only ever working with Azure AD:
- services.AddAzureAD
This line of code would ensure that your code works with both AzureAD and any other Authorization Server that meets the Open Id Connect standard:
- services.AddOpenIdConnect
The latter option also has an Events class with a commonly used OnRedirectToIdentityProvider operation that you can use to override the CallbackPath and provide a full RedirectUri.
Azure AD endpoints are standards based so you do not strictly have to use AzureAD specific libraries. Out of interest, I have a Single Page App Azure code sample that uses a neutral library like this, so I know this technique works.
I am trying to create a dynamic link using the Ruby SDK. I believe I have everything right, but I'm getting a
Google::Apis::ServerError: Server error
When creating the URL
Could you help me figure out what I'm missing/doing wrong or if this is a Google issue ?
Assuming I have generates Oauth credentials requesting the appropriate scopes, I am doing
request = ::Google::Apis::FirebasedynamiclinksV1::CreateManagedShortLinkRequest.new(
dynamic_link_info: ::Google::Apis::FirebasedynamiclinksV1::DynamicLinkInfo.new(
domain_uri_prefix: Rails.application.secrets.firebase_dynamic_link_prefix,
link: campaign.linkedin_url,
),
suffix: ::Google::Apis::FirebasedynamiclinksV1::Suffix.new(
option: 'SHORT',
),
# name: "Linkedin acquisition URL of #{camp.utm_campaign_name} for #{camp.contractor.name} <#{camp.contractor.email}>",
name: "Test of generation",
)
# => <Google::Apis::FirebasedynamiclinksV1::CreateManagedShortLinkRequest:0x000021618baa88
# #dynamic_link_info=#<Google::Apis::FirebasedynamiclinksV1::DynamicLinkInfo:0x000021618bad80
# #domain_uri_prefix="https://example.page.link",
# #link="https://www.example.com/?invitation_code=example&signup=example&utm_campaign=example&utm_medium=example&utm_source=example">,
# #name="Test of generation",
# #suffix=#<Google::Apis::FirebasedynamiclinksV1::Suffix:0x000021618babf0
# #option="SHORT">
# >
link_service.create_managed_short_link(request)
def link_service
#link_service ||= begin
svc = ::Google::Apis::FirebasedynamiclinksV1::FirebaseDynamicLinksService.new
svc.authorization = oauth_service.credentials
svc
end
end
I know OAuth scopes seem to be working as previously I was getting
Google::Apis::ClientError: forbidden: Request had insufficient authentication scopes.
But I fixed it after increasing OAuth scopes to cover firebase. Also, my request seems correct, as when I try to omit one of the parameters (like the name) I'm getting appropriate validation errors like
Google::Apis::ClientError: badRequest: Created Managed Dynamic Link must have a name
My only clue, is that the create_managed_short_link actually takes more parameters. In the example given above, I also have substituted our real firebase prefix by example but I do own the real firebase prefix I am using, and link generation directly from the Firebase frontend console actually works.
I've updates my google sdk to the most recent version up to date
- google-api-client-0.30.3
Unfortunately generating managed short links through the REST API is not currently supported.
As stated here by someone who works(ed) in the dynamic links team itself.
For now we can only use CreateShortDynamicLinkRequest, however this endpoint does not allow to specify a custom_suffix (i.e. https://example.com/my-custom-suffix)
I have an entire API deployed and accessible with Swagger UI. It uses Basic Auth over HTTPS, and one can easily hit the Authorize button and enter credentials and things work great with the nice Try it out! feature.
However, I would like to make a public sandboxed version of the API with a shared username and password, that is always authenticated; that is, no one should ever have to bring up the authorization dialog to enter credentials.
I tried to enter an authorization based on the answer from another Stack Overflow question by putting the following code inside a script element on the HTML page:
window.swaggerUi.load();
swaggerUi.api.clientAuthorizations.add("key",
new SwaggerClient.ApiKeyAuthorization(
"Authorization", "Basic dXNlcm5hbWU6cGFzc3dvcmQ=", "header"));
However, when I hit the Try it out! button the authorization is not used.
What would be the proper way to go about globally setting the auth header on all endpoints, so that no user has to enter the credentials manually?
(I know that might sound like a weird question, but like I mention, it is a public username/password.)
If you use Swagger UI v.3.13.0 or later, you can use the following methods to authorize the endpoints automatically:
preauthorizeBasic – for Basic auth
preauthorizeApiKey – for API keys and OpenAPI 3.x Bearer auth
To use these methods, the corresponding security schemes must be defined in your API definition. For example:
openapi: 3.0.0
...
components:
securitySchemes:
basicAuth:
type: http
scheme: basic
api_key:
type: apiKey
in: header
name: X-Api-Key
bearerAuth:
type: http
scheme: bearer
security:
- basicAuth: []
- api_key: []
- bearerAuth: []
Call preauthorizeNNN from the onComplete handler, like so:
// index.html
const ui = SwaggerUIBundle({
url: "https://my.api.com/swagger.yaml",
...
onComplete: function() {
// Default basic auth
ui.preauthorizeBasic("basicAuth", "username", "password");
// Default API key
ui.preauthorizeApiKey("api_key", "abcde12345");
// Default Bearer token
ui.preauthorizeApiKey("bearerAuth", "your_bearer_token");
}
})
In this example, "basicAuth", "api_key", and "bearerAuth" are the keys name of the security schemes as specified in the API definition.
I found a solution, using PasswordAuthorization instead of ApiKeyAuthorization.
The correct thing to do is to add the following line into the onComplete handler:
swaggerUi.api.clientAuthorizations.add("basicAuth",
new SwaggerClient.PasswordAuthorization(
"8939927d-4b8a-4a69-81e4-8290a83fd2e7",
"fbb7a689-2bb7-4f26-8697-d15c27ec9d86"));
swaggerUi is passed to the callback so this is the value to use. Also, make sure the name of your auth object matches the name in the YAML file.
I'm using the froatsnook:shopify atmosphere package to create an embedded public app on Shopify. I currently have a couple issues:
1) Getting the access token from the "code" query parameter after a user authenticates. As it mentions in the docs here, I'm supposed to use authenticator.getPermanentAccessToken(code) but what I don't understand is how to get call authenticator if the "code" parameter appears on the callback route (at that point, the authenticator I instantiated on the client pre-auth route is out of scope).
2) The "oAuth" function callback is never called for some reason, even when assigning it to Shopify.onAuth on the server.
3) The difference between post_auth_uri and redirect_uri ?
// I call this during 'onBeforeAction' for iron-router
function beforeAuth (query) {
// is this necessary..?
console.assert(Meteor.isClient);
// get shop name like 'myshop' from 'myshop.shopify.com';
const shop = query.shop.substring(0, query.shop.indexOf('.'));
// use api_key stored in settings
var api_key = Meteor.settings.public.shopify.api_key;
// Prepare to authenticate
var authenticator = new Shopify.PublicAppOAuthAuthenticator({
shop: shop,
api_key: api_key,
keyset: 'default',
embedded_app_sdk: true,
redirect_uri: 'https://45a04f23.ngrok.com/testContent',
//post_auth_uri: ???
// This is doesn't seem to be getting
// called after clicking through the OAuth dialog
onAuth: function(access_token) {
ShopifyCredentials.insert({
shop: shop,
api_key: api_key,
access_token: access_token
});
}
});
// Should i use something different with iron-router?
location.href = authenticator.auth_uri;
// how do i get code in this scope???
// authenticator.getPermanentAccessToken(code);
}
There are a few issues with the way you are trying to set up the authenticator, although it's not really your fault because the way Scenario 3 works in the docs is not an 'out of the box' solution and requires a bunch of custom code, including your own handler (I can provide a gist if you REALLY want to build your own handler, but I suggest using the new server-side onAuth callback instead)
1. Specifying a redirect_uri overrides the package's default redirect_uri handler which is Meteor.absoluteUrl("/__shopify-auth").
So instead, completely remove redirect_uri and put your testContent url in post_auth_uri instead.
2. ShopifyCredentials does not exist in this package. If you want to use it that way, make sure you actually have defined a collection called 'ShopifyCredentials' and insert the record from the server, not the client. Note that you will still need to add a keyset on the server for the API methods to work. If you are using user accounts and would like to permanently store credentials, I suggest saving the credentials to the database and adding the keyset via a server-side onAuth callback.
3. authenticator.getPermanentAccessToken(code) isn't useful unless you are using your own handler. Instead, you can just get access_token from the onAuth callback.
Also keep in mind that if you ever need to reauthenticate from inside the embedded app, you need to use window.top.location.href to break out of the iframe.
If you want a complete, working boilerplate example with user accounts see my gist here:
Authentication with Accounts and Persistent Keysets
If you aren't using accounts, you can use this gist instead, but please note that you really need to come up with some way to check that the current client has permission to request the keyset for a given shop before going to production:
Authentication with Persistent Keysets