Firebase rewrite - Allow dots in url parameters - firebase-hosting

How should the rewrite rule be in order to allow dots in URL parameters.
For example I have an URL like this:
http://example.com/path?param=abc.def
This will result a 404 page. Current rule is:
"rewrites": [{
"source": "**",
"destination": "/index.html"
}

Related

JSON LD serialization from schema.org

The following example is given on the json-ld playground.
Person example(expanded):
json-ld:
{
"#context": "http://schema.org/",
"#type": "Person",
"name": "Jane Doe",
"jobTitle": "Professor",
"telephone": "(425) 123-4567",
"url": "http://www.janedoe.com"
}
after serializtion to expanded:
[
{
"#type": [
"http://schema.org/Person"
],
"http://schema.org/jobTitle": [
{
"#value": "Professor"
}
],
"http://schema.org/name": [
{
"#value": "Jane Doe"
}
],
"http://schema.org/telephone": [
{
"#value": "(425) 123-4567"
}
],
"http://schema.org/url": [
{
"#id": "http://www.janedoe.com"
}
]
}
]
I ask myself where does the serializer gets the information to map the properties to the right subsequent schema (name). To achieve that, it must be able to get hold on to the person json ld schema. But if I go to https://schema.org/Person I get an HTML back and not a JSON-LD file.
So where does the serialization knowledge come from?
Jay is correct that the knowledge comes from the #context. This can be specified in a couple of ways:
Inline, using an object value for #context (or an array, which includes an object),
By directly retrieving a context from the URL specified (e.g., https://json-ld.org/contexts/person.jsonld),
By having the server do context-negotiation on the request, as an HTTP request includes an Accept header preferring JSON-LD (see Interpreting JSON as JSON-LD) such as the following:
GET /ordinary-json-document.json HTTP/1.1
Host: example.com
Accept: application/ld+json,application/json,*/*;q=0.1
Or, as is presently deployed by schema.org, by returning a Link header along with a GET or HEAD request identifying the location of the actual context to load (See Alternate Document Location):
HTTP/1.1 200 OK
...
Content-Type: text/html
Link: <alternate.jsonld>; rel="alternate"; type="application/ld+json"
This last case is used by schema.org because of the challenges of making HTTP Content-Negotiation work properly on certain static site generators. If you to a HEAD request at https://schema.org, you'll get back headers including the following:
HTTP/2 200
link: </docs/jsonldcontext.jsonld>; rel="alternate"; type="application/ld+json"
A conforming JSON-LD processor (such as on the json-ld.org playground) knows to follow this link to find the actual context.
In the case of the Person example, "Person" and the other keys are turned into IRIs based on instructions in that context file such as the following:
{
"#context": {
...
"schema": "http://schema.org/",
"Person": {"#id": "schema:Person"},
"name": { "#id": "schema:name"},
"jobTitle": { "#id": "schema:jobTitle"},
"telephone": { "#id": "schema:telephone"},
"url": { "#id": "schema:url", "#type": "#id"},
...
}
}
Note that in the case of "url", it also knows that the value of that property should be treated as an IRI, rather than a text string.

LinkedIn API :: how to obtain the bearer access token

It's not easy to use the official LinkedIn API and I cannot find a valid documentation.
Following the official documentation I created a new application in order to obtain the Client ID and Client Secret
When I now make a POST call through Postman to https://www.linkedin.com/oauth/v2/accessToken this is what I obtain:
{
"error": "invalid_grant_type",
"error_description": "The passed in grant_type is invalid"
}
Where am I wrong?
EDIT AFTER HELP FROM #Amit Singh
Thanks to #AmitSingh I was able to create 2 different applications, the test with the Client Credentials flow gave me as a result an error retrieving the token:
{
"error": "access_denied",
"error_description": "This application is not allowed to create application tokens"
}
When I try to use the LinkedIn 3-legged workflow I receive Unauthorized
EDIT 3: GETTING THERE THROUGH POSTMAN
I now see that I can ask Postman to do the job, however when I press on Get New Access Token it opens an error page. I believe the error might be in these 4 elements:
Token name: maybe I have to give a special token name?
Auth URL: I set https://www.getpostman.com/oauth2/callback as explained here but maybe I have to set something else?
Access Token URL: I left it blank, maybe I have to put something here?
State: I set a random string like Hello123Boy but maybe I have to put something else. Maybe is too long. Maybe is too short. Maybe it has to contain symbols, etc... ?
...Also, in the guide you linked it says that the applicatoin needs to have:
r_liteprofile
rw_company_admin
w_member_social
mine has nothing:
Being recently created is still under review. It says it can take up to 90 days. Is that true?
4th EDIT: I WANT TO BELIEVE!
Here we are, at least now I'm getting the error: Bummer, something went wrong. The redirect_uri does not match the registered value. This is amazing: finally an error that says where the problem is!
On the app the, on the Products tab, I choose Sign In with LinkedIn. As
Authorized redirect URLs for your app I set https://www.getpostman.com/oauth2/callback
In Postman I setup Auth URL and Access Token URL as you said:
LinkedIn Credential workflows
LinkedIn offers 2 different credential workflows.
LinkedIn 3-legged workflow - When you want to use an API that will access LinkedIn member's data. Authorization Code grant type needed.
LinkedIn Client Credentials flow - When you want to use an API that will access non-member resources. Client credentials grant needed.
What are grant types?
"Grant type" refers to how you have acquired an access token in an OAuth workflow.
Several grant types are supported. Some of them are:
Client Credentials - Used when you want to access your own resources and not any other users
Authorization Code - Used when an app wants to access a client's data
Refresh token - Exchange an expired access token for a valid access token, used to avoid repeated user involvement
Password - Used when there is high trust between the app and the user e.g. LinkedIn mobile app, you provide your username and password
Client Credentials flow
What you need to know
Grant type used here is Client credentials - client_credentials.
Remember to set your Content-Type to application/x-www-form-urlencoded for all POST requests in OAuth.
Steps
Create an App and get your Client ID and Client Secret. Steps are shown in the respective docs linked above. Let's say they have values - <client_id> and <client_secret>.
Send a POST required to https://www.linkedin.com/oauth/v2/accessToken with following information.
Parameters
grant_type : client_credentials
client_id : <client_id>
client_secret : <client_secret>
NOTE : client_credentials is the literal text to be entered for grant_type.
Response will return a JSON Object containing your access token and its expiry duration in seconds.
Response
{
"access_token" : <access_token>,
"expires_in" : "1800"
}
Use the <access_token> obtained in Step 2 make API requests.
Example
Request URL: https://www.linkedin.com/v2/jobs
Request type: GET
Parameters
Authorization: Bearer <access_token>
LinkedIn 3-legged workflow
What you need to know
Grant type will be Authorization code - code, since you want to access a user's data.
Your Content-Type should be application/x-www-form-urlencoded for all POST requests in OAuth.
Redirect URLs are URLs where you OAuth server will redirect the user after successful authorization.
These are verified against your provided redirect URLs to ensure that it's not fraudulent.
These should be absolute URLs.
URL arguments are ignored and cannot include a #.
Steps
Create app and provide the Redirect URLs, if not already provided. Check docs for information regarding how to do this.
Get your Client ID and Client Secret. Let's say the values are <client_id> and <client_secret>.
Generate a random, hard to guess string. Let's say it's <random-string>.
Choose one of the redirect URLs provided in Step 1, where you want user to be redirected after authorization. Let's say it is <redirect_uri>.
Let's suppose you want to:
r_emailaddress - Get his email address
w_member_social - Post, comment and like posts on behalf of the user.
These are referred as "permission scopes", as in what permissions is the user authenticating you for. When sending these scopes in your request, they should be URL-encoded and space-delimited. In this particular instance, our scope will be scope: r_emailaddress%20w_member_social. We have URL-encoded the scopes mentioned above.
Adding more information regarding scopes from the Microsoft docs:
The scopes available to your app depend on which Products or Partner Programs your app has access to. Your app's Auth tab will show current scopes available. You can apply for new Products under the Products tab. If approved, your app will have access to new scopes.
Send a POST request to https://www.linkedin.com/oauth/v2/authorization with following information.
Parameters
response_type : code
client_id : <client_id>
redirect_uri : <redirect_uri>
state : <random_string>
scope : r_emailaddress%20w_member_social
After the request, the user will be presented with LinkedIn's Auth screen and asked to approve the request.
After user approves the request and the <redirect_uri> has been verified, user will be redirected to provided <redirect_uri> along with the access code <access_code> and a value in state argument. Let's say in the state argument is <state_value>.
Verify that the <state_value> is equal to the <random_string> before working with the <access_code> to get access token, for security purposes. Also, use the <access_code> within 30 minutes of being issued, for security reasons.
Next, send a POST request to https://www.linkedin.com/oauth/v2/accessToken with following information to get the access token.
Parameters
grant_type : authorization_code
client_id : <client_id>
client_secret : <client_secret>
redirect_uri : <redirect_uri>
code : <access_code>
NOTE : authorization_code is the literal text to be passed in grant_type.
You should get a similar response as in the Client Credentials workflow containing your access token and expiry duration.
Response
{
"access_token" : <access_token>,
"expires_in" : "1800"
}
Use the <access_token> obtained in Step 9 make API requests.
Example
Request URL: `https://www.linkedin.com/v2/me`
Request type: GET
Parameters:
Authorization: Bearer <access_token>
How to do this in Postman?
Create a new Collection.
Right click, select edit collection and move to authorization tab.
In "Type", select "OAuth2.0", click on "Get New Access Token".
You will see a screen where all the familiar terms mentioned above are there. Fill those, check the "Authorize via Browser" checkbox for authorization.
Now you have the access token and can proceed to make your API calls.
Postman has been designed to make such operations easier, but you have to know how to do it. For more details, you can read their official docs.
Thanks to #timur and #AmitSingh I finally arrived to authenticate to LinkedIn API.
A brief step by step solution in pictures:
Authorized redirect URLs for your app = https://oauth.pstmn.io/v1/callback
OAuth 2.0 scopes = must have r_emailaddress and r_liteprofile
In the Products Tab set Sign In with LinkedIn
Now open Postman > Collections > New Collection > Authorization and set the parameters as in picture:
TYPE = OAUTH 2.0
Token Name = put whatever you want
Callback URL = https://oauth.pstmn.io/v1/callback (should be greyed out once you tick Authorize using browser)
Tick Authorize using browser
Auth URL = https://www.linkedin.com/oauth/v2/authorization
Access Token URL = https://www.linkedin.com/oauth/v2/accessToken
Client ID = The Client ID you find on your LinkedIn App
Client Secret = The Client Secret you find on your LinkedIn App
Scope = r_liteprofile r_emailaddress
State = put whatever you like
Now click on Get New Access Token, a page will open on your browser and you will be able to login using your LinkedIn account. Once done you are authenticated.
Now use the code provided by #timur and on Postman go to Import > Upload File and import that .JSON file. You will now have the 4 queries and you can drag and drop them in your collection.
Assuming you've created your app, added correct redirect URL and enabled "Sign In with LinkedIn" product for your app, the issue you are having is probably that the first call returns a login page where your users are supposed to authenticate.
submit the request to https://www.linkedin.com/oauth/v2/authorization (you seem to have done that)
parse response of step 1 and extract all form values, add username and password to simulate user login
make POST request and use values from previous step as x-www-form-urlencoded data
manually follow the redirect header from step 3
make note of second redirect header but do not follow it - instead extract the code
POST code from previous step to https://www.linkedin.com/oauth/v2/accessToken and get access_token in response
From here, I was able to successfully transition to the auth code by following the steps.
I am not sure if you use the online Postman, but here's my complete collection export file for reference:
{
"info": {
"_postman_id": "397761c9-4287-43f2-860a-3c34cb710d50",
"name": "Linkedin oAuth",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "01 request Login form",
"event": [
{
"listen": "test",
"script": {
"exec": [
"const $ = cheerio.load(pm.response.text());\r",
"var inputs = $('form').serializeArray();\r",
"var payload = '';\r",
"inputs.forEach(i => {\r",
" payload += encodeURIComponent(i.name)+ '=' + encodeURIComponent(i.value) + '&';\r",
"})\r",
"payload += 'session_key='+ encodeURIComponent(pm.collectionVariables.get('username')) + '&'\r",
"payload += 'session_password='+ encodeURIComponent(pm.collectionVariables.get('password'))\r",
"\r",
"pm.collectionVariables.set(\"form_data\", payload);"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id={{client_id}}&redirect_uri={{redirect_uri}}&scope=r_liteprofile&state={{$guid}}",
"protocol": "https",
"host": [
"www",
"linkedin",
"com"
],
"path": [
"oauth",
"v2",
"authorization"
],
"query": [
{
"key": "response_type",
"value": "code"
},
{
"key": "client_id",
"value": "{{client_id}}"
},
{
"key": "redirect_uri",
"value": "{{redirect_uri}}"
},
{
"key": "scope",
"value": "r_liteprofile"
},
{
"key": "state",
"value": "{{$guid}}"
}
]
}
},
"response": []
},
{
"name": "02 Submit login form",
"event": [
{
"listen": "prerequest",
"script": {
"exec": [
""
],
"type": "text/javascript"
}
},
{
"listen": "test",
"script": {
"exec": [
"var url = 'https://www.linkedin.com'+ pm.response.headers.get(\"Location\");\r",
"pm.collectionVariables.set('first_redirect', url);\r",
"//console.log(pm.collectionVariables.get('first_redirect'));"
],
"type": "text/javascript"
}
}
],
"protocolProfileBehavior": {
"followRedirects": false
},
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/x-www-form-urlencoded",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "{{form_data}}",
"options": {
"raw": {
"language": "text"
}
}
},
"url": {
"raw": "https://www.linkedin.com/checkpoint/lg/login-submit",
"protocol": "https",
"host": [
"www",
"linkedin",
"com"
],
"path": [
"checkpoint",
"lg",
"login-submit"
]
}
},
"response": []
},
{
"name": "03 handle login-success redirect",
"event": [
{
"listen": "test",
"script": {
"exec": [
"var sdk = require('postman-collection');\r",
"var redirect = new sdk.Url(pm.response.headers.get(\"Location\"));\r",
"pm.collectionVariables.set('code', redirect.query.filter(q => q.key === 'code').map(k => k.value)[0]);\r",
"//console.log(pm.collectionVariables.get('code'));"
],
"type": "text/javascript"
}
},
{
"listen": "prerequest",
"script": {
"exec": [
"console.log(pm.variables.get('first_redirect'));\r",
"pm.request.url.update(pm.variables.get('first_redirect'));"
],
"type": "text/javascript"
}
}
],
"protocolProfileBehavior": {
"followRedirects": false
},
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{first_redirect}}",
"host": [
"{{first_redirect}}"
]
}
},
"response": []
},
{
"name": "04 Get Auth Code",
"request": {
"method": "POST",
"header": [],
"url": {
"raw": "https://www.linkedin.com/oauth/v2/accessToken?grant_type=authorization_code&code={{code}}&redirect_uri={{redirect_uri}}&client_id={{client_id}}&client_secret={{client_secret}}",
"protocol": "https",
"host": [
"www",
"linkedin",
"com"
],
"path": [
"oauth",
"v2",
"accessToken"
],
"query": [
{
"key": "grant_type",
"value": "authorization_code"
},
{
"key": "code",
"value": "{{code}}"
},
{
"key": "redirect_uri",
"value": "{{redirect_uri}}"
},
{
"key": "client_id",
"value": "{{client_id}}"
},
{
"key": "client_secret",
"value": "{{client_secret}}"
}
]
}
},
"response": []
}
],
"event": [
{
"listen": "prerequest",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
},
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
}
],
"variable": [
{
"key": "client_id",
"value": "your app id"
},
{
"key": "client_secret",
"value": "your app secret"
},
{
"key": "redirect_uri",
"value": "your urlencoded redirect uri such as https%3A%2F%2Flocalhost%3A8080"
},
{
"key": "username",
"value": "user login"
},
{
"key": "password",
"value": "user password"
}
]
}

Unable to add Firebase Dynamic Links to a project using a custom domain

I've created Firebase Dynamic Links in other projects before using the page.link URL prefix (the only option at the time). However that no longer appears to be an option when enabling Dynamic Links for a new project today. It looks like the docs for custom domains were updated today. Unfortunately, while they provide some helpful examples, they don't explain how to resolve my issue in a way that I can understand.
So this message seems to indicate that I need to "Put links under a subpath". It seems to me that adding the /link path prefix here should accomplish that? But after making that change, I get the same error message.
It looks like you already have content served on this Hosting site. Put links under a subpath to avoid conflicts with existing content.
I order to try to work around this, I went to my project and added the suggested Hosting configuration and deployed it.
firebase.json:
{
"hosting": {
"public": "dist",
"ignore": [
"**/.*"
],
"appAssociation": "AUTO",
"rewrites": [
{
"source": "/link/**",
"dynamicLinks": true
},
{
"source": "**",
"destination": "/index.html"
}
]
}
}
Even after waiting an hour, I keep getting the same error and I don't understand what to do.
Note that I have the rewrite to /index.html because I have an Angular Single Page Application hosted at www.devintent.com.
No matter what I do, I keep getting this error and the Continue button stays disabled. I tried using my naked domain devintent.com instead of www.devintent.com, but that didn't help. I tried using links.devintent.com, but got the same error.
If I manually remove disabled from the Continue button, then I get this error message:
This links to the docs for manually configuring hosting for dynamic links which has some steps that I've already completed. Clicking the Check Again button just takes me back to the same error in the first image above.
Workaround to not use Custom Domains
I'm happy at this time to use a page.link link, I just need to create a Dynamic Link and get unblocked on this task, but I can't figure out any way in the console to allow me to do that either.
Update on this: I figured out that I needed to add the subdomain to the page.link URL. The tooltip says "or a free Google-provided domain (for example, yourapp.page.link)" at the end. This is a little less prominent that I would have liked (as I didn't find it for quite a long time). Using devintent.page.link works for me as a workaround for now.
However, I'm not sure why the Custom Domain configuration didn't work and why it would be the default if there are such complications with projects with existing Hosting configurations and custom domains.
I had the same issue and got the same error. The problem was that I was redirecting all the routes to index.html. The solution was to restrict the routes to index.html by exclusion.
"rewrites": [
{
"source": "/link/**",
"dynamicLinks": true
},
{
"source": "!/link/**",
"destination": "/index.html"
}
]
After deploying the new configuration to Firebase Hosting, I was allowed to use mydomain.example/link as desired.
I had a similar problem with the root (apex) domain. Basically, if the prefix (the apex domain in my case) is a URL that gives 200 status response, it won't be accepted. In my case the there was an index.html file inside the public folder. I renamed it to something else and it worked.
Here's my firebase.json:
{
"hosting": {
"public": "public",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"appAssociation": "AUTO",
"rewrites": [ { "source": "/**", "dynamicLinks": true } ]
}
}
Everything you are doing looks correct with regards to getting your custom domain working. I am wondering if you are using the latest version of firebase-tools (CLI). Custom Domain support for dynamic links was added in version 6.5.0.
Good news is that page.link support still exists but is not as obvious in the new UI flow. The way to get a page.link domain is to start typing your desired subdomain and a suggestion should pop up in the UI. I have included a screenshot to show how this would work.
Adding page link domain image
Hope this helps,
Jeff
For us the issue was that we we're using the default Firebase Hosting site for serving our web-app at all routes ("/**"). Serving our web-app from a different domain that dynamic links is the way it should be for us, since links should not interfere with the web-app.
To serve links from a different domain than our web-app, I created a new site just for links and attached our custom domain to that site. Now we have two Firebase Hosting sites. The first default one for our web-app and the second one for Firebase Dynamic Links.
After this setting up Dynamic Links with a custom domain attached to the second Firebase Hosting site worked flawlessly.
See the screenshot for more details:
EDIT1: the web-app's firebase.json as requested by #cocacrave:
{
"hosting": {
"public": "public",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [
{
"source": "**",
"function": "nuxtApp"
}
]
}
}
If you are getting this error with subdomain, this solution worked for me:
{
"hosting": [
{
"target": "app",
"public": "build",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"appAssociation": "AUTO",
"rewrites": [
{
"source": "/**",
"destination": "/index.html"
}
]
},
{
"target": "links",
"public": "build",
"ignore": [
"**"
],
"appAssociation": "AUTO",
"redirects": [
{
"source": "/",
"destination": "{{your domain}}",
"type": 302
}
],
"rewrites": [
{
"source": "/**",
"dynamicLinks": true
}
]
},
{
"target": "invite",
"public": "build",
"ignore": [
"**"
],
"redirects": [
{
"source": "/",
"destination": "{{your domain}}",
"type": 302
}
],
"appAssociation": "AUTO",
"rewrites": [
{
"source": "/**",
"dynamicLinks": true
}
]
}
]
}
"appAssociation": "AUTO",
"rewrites": [
{
"source": "/page/**",
"destination": "/index.html"
},
{
"source": "/link/**",
"dynamicLinks": true
}
]
It works! I guess the error caused by the original config of "source":"**". May be the range is too large? Though doc say "/__/*" have high priority. (https://firebase.google.com/docs/hosting/full-config?authuser=0#hosting_priority_order)

How to specify what operations (Http Methods) is available with HATEOAS

The HATEOAS definition say that each response must contains the associated links, but following the REST pattern an URI must be the same for all operations, what changes is the HTTP method.
Per example:
Request: GET http://example.com/book
Response:
[
{
"id": 1,
"title:" "foo",
"links": {
"self": http://example.com/book/1
}
}
]
The response inform that the link that can be used to read the book data is "http://example.com/book/1", but with only this link i unknown if the user that did this request is authorized to do a DELETE or PUT for this resource.
The HATEOAS or other specification define some pattern for inform what methods are available for each resource?
Per example:
Request: GET http://example.com/book
Response:
[
{
"id": 1,
"title:" "foo",
"links": {
"self": {
"url": "http://example.com/book/1",
"methods": [
"GET",
"PUT",
"DELETE"
]
}
}
]
You should look for Siren (http://hyperschema.org/mediatypes/siren)
It will tell your clients what methods do they can use with links.

Should links in json-api responses be encoded?

Should the query params in links appearing in JSON-API responses be percent-encoded?
The examples from jsonapi.org are not encoded, as in:
{
"links": {
"self": "http://example.com/articles",
"next": "http://example.com/articles?page[offset]=2",
"last": "http://example.com/articles?page[offset]=10"
},
"data": [{
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON API paints my bikeshed!"
}
]
}
However, there is also a note regarding encoding parameters in requests:
GET /articles?include=author&fields[articles]=title,body&fields[people]=name HTTP/1.1
Accept: application/vnd.api+json
Note: The above example URI shows unencoded [ and ] characters simply for readability. In practice, these characters must be percent-encoded, per the requirements in RFC 3986.
Does this note apply only to requests? Or should responses also be percent-encoded, as in:
{
"links": {
"self": "http://example.com/articles",
"next": "http://example.com/articles?page%5Boffset%5D=2",
"last": "http://example.com/articles?page%5Boffset%5D=10"
},
"data": [{
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON API paints my bikeshed!"
}
]
}
Yes, the note about encoding URI's in your question applies only to the request, not the response
With a json string returned inside a response, the only thing required to escape is the double quote character "
GET requests (not responses) are a different ball of wax. Anything in a GET request passed as a parameter in the URL must be URL encoded. So if you have a parameter url=http://some.url.com, the url on the right of the parameter assignment needs encoding.
It's tricky with POST and PUT requests. Depending on the content type set in the header you may need to encode. If your content type is application/json, you should not need to url encode anyting in your json string, (excluding the before mentioned ").
Now, if the specificed content encoding you state doesn't match what your sending (or if you didn't explicitly add one and it defaults to something), like if you send content-type: application/x-www-form-urlencoded but send a json string, the API service may or may not accept it and who know what how it will treat the content inside as far a url decoding it.