<p>I am trying to create a download link to an ASSET in craft that I can send via email - the client clicks the email link (which has a token query string attached to validate the link) and is presented with a Craft page to download the attachment.</p>
<p><strong>But</strong> I keep getting a 404 error, saying invalid route.</p>
<ul>
<li>Im trying to create a token - which will only last for say 24H.</li>
</ul>
<p><code>{% set token = craft.app.tokens.createToken() %}</code></p>
<p>I am aware that you need to pass in arguments to createToken() but I am not sure what they are as the docs are somewhat highlevel:</p>
<p><a href="https://docs.craftcms.com/api/v4/craft-services-tokens.html#public-methods" rel="nofollow noreferrer">https://docs.craftcms.com/api/v4/craft-services-tokens.html#public-methods</a></p>
<ul>
<li><p>Store the token in Craft <em>(haven't worked this part out yet)</em></p>
</li>
<li><p>Send a link, with the token as part of a query string</p>
</li>
</ul>
<p><code>&lt;a href=&quot;&lt;PAGE_URL&gt;?token={{ token }}&quot;&gt;Download here&lt;/a&gt;</code></p>
<ul>
<li><p>When the user visits the page, the twig template <strong>1) gets the query string:</strong></p>
<p>{% set token_from_queryString = craft.app.request.getQueryParams('token')|first %}</p>
</li>
<li>
<ol start="2">
<li>Validates that the query string is the same as the one stored and so presents the download link to the user:</li>
</ol>
</li>
</ul>
  
    
      <h2>LOGIC TEST</h2>
<pre><code>  &lt;div&gt;
    {% if token == token_from_queryString %}
      &lt;p&gt;Token matches - download file:&lt;/p&gt;
    {% else %}
      &lt;p&gt;Seems that the link used is no longer acspected, please create a new one:&lt;/p&gt;
    {% endif %}
  &lt;/div&gt;  
&lt;/div&gt;
</code></pre>
  
<p>Issues:</p>
<ol>
<li>I don't know if this is actually viable using this option.</li>
<li>if I do not include the query string in the URL i.e. do not add ?token=(token value or a fake value) - then the page works as exspected. But if I add the query string I get given a 404 route error.
How am I supposed to be formatting the tokens route, is this were the token lives? or a route to be directed to via the link. I haven't found any Craft docs on this as of yet.</li>
</ol>
<p>-- Update of work:</p>
<p>I have got most of this code to work, but still am having issues with the route of the TOKEN and hiding the original URL of the ASSET.</p>
<p>ISSUES:</p>
<p>A) Does Craft automatically store tokens, if so where? I saw reference on discord to storing them in the DB, but no docs to back this up yet.</p>
<p>B) I set the route to be the file URL in hopes it could be used in a <code>&lt;a href=&quot;{{ tokenA }}&quot; download&gt;{{ download.title }}&lt;/a&gt;</code>, but this says that the  file cannot be found. But if I use &quot;getToken&quot; variable below, that uses <code>getTokenRoute()</code>, then it spits out the original URL of the download. So the token route is set to the URL (maybe I could be misunderstanding), but the token cannot be used to route to a download path.</p>
<p>This is all with the goal of validating the user via a token and hiding the URL of the original download file.</p>
<p>URL needing to be used:</p>
<pre><code>https://&lt;your site address&gt;/&lt;page with this code on it&gt;?tokenA=&lt;the token number produced by tokenA (also printed to the page at TOKEN CODE:&gt;
</code></pre>
<p>Code below</p>
<pre><code>&lt;main&gt;
&lt;h2&gt;TOKENs&lt;/h2&gt;

&lt;div class=&quot;wrapper&quot; style=&quot;background:lightblue&quot;&gt;
  {# Get file #}
  {% set download = craft.assets().fileName('&lt;NAME OF FILE TO BE USED AS ONE TIME DOWNLOAD&gt;').one() %}

  {# Create token - ISSUE A) B) #}
  {% set tokenA = craft.app.tokens.createToken([download.url]) %}

  {# Gets the Token from the query string #}
  {% set queryString = craft.app.request.getQueryParams('tokenA')|first %}

  {# Takes the original token path - and gets it #}
  {# R) download path of the asset - download #}
  {% set getToken = craft.app.tokens.getTokenRoute(queryString)|first %}

  &lt;p&gt;TOKEN CODE: {{ tokenA }}&lt;/p&gt;
  &lt;a href=&quot;{{ tokenA }}&quot;&gt;Download&lt;/a&gt;
  &lt;p&gt;Result of get token: {{ getToken }}&lt;/p&gt;
&lt;/div&gt;

&lt;div class=&quot;wrapper&quot;&gt;
  &lt;h2&gt;Params&lt;/h2&gt;
  &lt;div class=&quot;wrapper&quot; style=&quot;background:lightskyblue&quot;&gt;
    &lt;h1&gt;{{ getToken }}&lt;/h1&gt;

    {# If getToken has length i.e. it has been found #}
    {% if getToken|length %}
      &lt;h1&gt;Working&lt;/h1&gt;
      {# &lt;a href=&quot;{{ getToken }}&quot;&gt;link&lt;/a&gt; #}
    {% else %}
      &lt;h1&gt;Broken&lt;/h1&gt;
    {% endif %}

      {# &lt;p&gt;getQueryString(): {{ param }}&lt;/p&gt;
      &lt;p&gt;getQueryParams(): &lt;br&gt; 
        {% for block in param2 %}
          {{ block }}&lt;br&gt;
        {% endfor %}
      &lt;/p&gt;
      &lt;p&gt;&lt;strong&gt;getQueryParams('token')|first: {{ param4 }}&lt;/strong&gt; &lt;&lt;&lt; WINNER&lt;/p&gt;
      &lt;p&gt;getQueryStringWithoutPath(): {{ param3 }}&lt;/p&gt; #}
    
  &lt;/div&gt;
&lt;/div&gt;



&lt;div class=&quot;wrapper&quot;&gt;
  &lt;h2&gt;LOGIC TEST&lt;/h2&gt;

  &lt;div class=&quot;wrapper&quot; style=&quot;background:lightyellow&quot;&gt;
    {% if getToken|length %}
      {# If token is valid allow the user to download the below - #}
      {# ISSUE: need to hide the original url of the download so it cannot be shared - #}
      &lt;a href=&quot;{{ download.url }}&quot; download&gt;{{ download.title }}&lt;/a&gt;

      {# Delete the used token from the system (this doens't seem to be working) #}
      {{ craft.app.token.deleteTokenById(getToken) }}
    {% else %}
      &lt;p&gt;Seems that the link used is no longer acspected, please create a new one:&lt;/p&gt;
    {% endif %}
  &lt;/div&gt;  
&lt;/div&gt;
</code></pre>


CC BY-SA 4.0
2022-09-21T17:26:29.137Z
This _seems_ like it’d be a viable approach, where you’d want to store the token’s ID after creating it. That way, you could retrieve its value with `getTokenById()`. For issue #2, have you tried `&token=[value]` instead of `?token=[value]`? Don’t know offhand how the route should be formatted as an argument, but you’ll probably find examples in Craft’s source.
Matt Stein
CC BY-SA 4.0
2022-09-22T08:16:26.850Z
Hi Matt, thanks for the help - turns out there is very little mention of createToken in the Craft source files - which has also been mentioned on the discord. Adding the & instead of the ? does work, but I think it now believe the path should include the token as part of the sites address and not a query string. I'll update the question with an example.
Wally
CC BY-SA 4.0
2022-09-22T17:19:02.970Z
To be fair, you’re working with something Craft uses more internally. The good news is there are examples in the source to follow, like this: https://github.com/craftcms/cms/blob/f45c185f4aa0d164f40fbdb34061d0150256ef1c/src/console/controllers/UsersController.php#L414-L419

You could mimic that pattern, including the use of that `UrlHelper::urlWithToken()` method to build the URL.
Matt Stein
CC BY-SA 4.0
2022-09-22T18:07:35.400Z
@MattStein - thanks again for the reply and looking over the question, its really appreciated. I also, found out why the ?token= was not working. I had stupidly set a variable to the name of "token" which Craft seemed to dislike rather alot.
Wally
CC BY-SA 4.0
2022-09-22T23:44:22.233Z
I dunno @Wally, “token” sounds reasonable to me even if it turned out to be a problem. It sounds like you’re closer to the goal, at least!
Matt Stein
<api><token>
Using tokens to authenticate a one-time download link?
# Using tokens to authenticate a one-time download link?

(Asked by: username_0 on 2022-09-20)

I am trying to create a download link to an ASSET in craft that I can send via email - the client clicks the email link (which has a token query string attached to validate the link) and is presented with a Craft page to download the attachment.
But I keep getting a 404 error, saying invalid route.

Im trying to create a token - which will only last for say 24H.

{% set token = craft.app.tokens.createToken() %}
I am aware that you need to pass in arguments to createToken() but I am not sure what they are as the docs are somewhat highlevel:
https://docs.craftcms.com/api/v4/craft-services-tokens.html#public-methods

Store the token in Craft (haven't worked this part out yet)

Send a link, with the token as part of a query string


Download here

When the user visits the page, the twig template 1) gets the query string:
{% set token_from_queryString = craft.app.request.getQueryParams('token')|first %}



Validates that the query string is the same as the one stored and so presents the download link to the user:



LOGIC TEST
  
    {% if token == token_from_queryString %}
      Token matches - download file:
    {% else %}
      Seems that the link used is no longer acspected, please create a new one:
    {% endif %}
    


Issues:

I don't know if this is actually viable using this option.
if I do not include the query string in the URL i.e. do not add ?token=(token value or a fake value) - then the page works as exspected. But if I add the query string I get given a 404 route error.
How am I supposed to be formatting the tokens route, is this were the token lives? or a route to be directed to via the link. I haven't found any Craft docs on this as of yet.

-- Update of work:
I have got most of this code to work, but still am having issues with the route of the TOKEN and hiding the original URL of the ASSET.
ISSUES:
A) Does Craft automatically store tokens, if so where? I saw reference on discord to storing them in the DB, but no docs to back this up yet.
B) I set the route to be the file URL in hopes it could be used in a {{ download.title }}, but this says that the  file cannot be found. But if I use "getToken" variable below, that uses getTokenRoute(), then it spits out the original URL of the download. So the token route is set to the URL (maybe I could be misunderstanding), but the token cannot be used to route to a download path.
This is all with the goal of validating the user via a token and hiding the URL of the original download file.
URL needing to be used:
https:///?tokenA=

Code below

TOKENs


  {# Get file #}
  {% set download = craft.assets().fileName('').one() %}

  {# Create token - ISSUE A) B) #}
  {% set tokenA = craft.app.tokens.createToken([download.url]) %}

  {# Gets the Token from the query string #}
  {% set queryString = craft.app.request.getQueryParams('tokenA')|first %}

  {# Takes the original token path - and gets it #}
  {# R) download path of the asset - download #}
  {% set getToken = craft.app.tokens.getTokenRoute(queryString)|first %}

  TOKEN CODE: {{ tokenA }}
  Download
  Result of get token: {{ getToken }}



  Params
  
    {{ getToken }}

    {# If getToken has length i.e. it has been found #}
    {% if getToken|length %}
      Working
      {# link #}
    {% else %}
      Broken
    {% endif %}

      {# getQueryString(): {{ param }}
      getQueryParams():  
        {% for block in param2 %}
          {{ block }}
        {% endfor %}
      
      getQueryParams('token')|first: {{ param4 }} <<< WINNER
      getQueryStringWithoutPath(): {{ param3 }} #}
    
  





  LOGIC TEST

  
    {% if getToken|length %}
      {# If token is valid allow the user to download the below - #}
      {# ISSUE: need to hide the original url of the download so it cannot be shared - #}
      {{ download.title }}

      {# Delete the used token from the system (this doens't seem to be working) #}
      {{ craft.app.token.deleteTokenById(getToken) }}
    {% else %}
      Seems that the link used is no longer acspected, please create a new one:
    {% endif %}

**Tags:** api, token
> **Comments:**
> **username_1**: This _seems_ like it’d be a viable approach, where you’d want to store the token’s ID after creating it. That way, you could retrieve its value with `getTokenById()`. For issue #2, have you tried `&token=[value]` instead of `?token=[value]`? Don’t know offhand how the route should be formatted as an argument, but you’ll probably find examples in Craft’s source.  
> **username_0**: Hi Matt, thanks for the help - turns out there is very little mention of createToken in the Craft source files - which has also been mentioned on the discord. Adding the & instead of the ? does work, but I think it now believe the path should include the token as part of the sites address and not a query string. I'll update the question with an example.  
> **username_1**: To be fair, you’re working with something Craft uses more internally. The good news is there are examples in the source to follow, like this: https://github.com/craftcms/cms/blob/f45c185f4aa0d164f40fbdb34061d0150256ef1c/src/console/controllers/UsersController.php#L414-L419  You could mimic that pattern, including the use of that `UrlHelper::urlWithToken()` method to build the URL.  
> **username_0**: @MattStein - thanks again for the reply and looking over the question, its really appreciated. I also, found out why the ?token= was not working. I had stupidly set a variable to the name of "token" which Craft seemed to dislike rather alot.  
> **username_1**: I dunno @Wally, “token” sounds reasonable to me even if it turned out to be a problem. It sounds like you’re closer to the goal, at least!  

---

## 0 answers

